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 string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
35 string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
36 string autocvar_hud_panel_scoreboard_bg = "border_default";
37 string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
38 string autocvar_hud_panel_scoreboard_bg_color_team = "";
39 string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
40 string autocvar_hud_panel_scoreboard_bg_border = "";
41 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.5;
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);
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 = ftos(pl.(scores(ps_primary)));
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 max_players = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1014 max_players = (max_players - hud_fontsize.y * 1.25 - panel_bg_padding * 2) / 2;
1015 max_players = floor(max_players / (hud_fontsize.y * 1.25));
1016 if(max_players <= 1)
1018 if(max_players == tm.team_size)
1023 entity me = playerslots[current_player];
1025 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1026 panel_size.y += panel_bg_padding * 2;
1029 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1030 if(panel.current_panel_bg != "0")
1031 end_pos.y += panel_bg_border * 2;
1033 if(panel_bg_padding)
1035 panel_pos += '1 1 0' * panel_bg_padding;
1036 panel_size -= '2 2 0' * panel_bg_padding;
1040 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
1044 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1046 pos.y += 1.25 * hud_fontsize.y;
1049 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1051 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1054 // print header row and highlight columns
1055 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1057 // fill the table and draw the rows
1058 bool is_self = false;
1059 bool self_shown = false;
1061 for(pl = players.sort_next; pl; pl = pl.sort_next)
1063 if(pl.team != tm.team)
1065 if(i == max_players - 2 && pl != me)
1067 if(!self_shown && me.team == tm.team)
1069 Scoreboard_DrawItem(pos, rgb, me, true, i);
1071 pos.y += 1.25 * hud_fontsize.y;
1075 if(i >= max_players - 1)
1077 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1080 is_self = (pl.sv_entnum == current_player);
1081 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1084 pos.y += 1.25 * hud_fontsize.y;
1088 panel_size.x += panel_bg_padding * 2; // restore initial width
1092 bool Scoreboard_WouldDraw()
1094 if (MUTATOR_CALLHOOK(DrawScoreboard))
1096 else if (QuickMenu_IsOpened())
1098 else if (HUD_Radar_Clickable())
1100 else if (scoreboard_showscores)
1102 else if (intermission == 1)
1104 else if (intermission == 2)
1106 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1108 else if (scoreboard_showscores_force)
1113 float average_accuracy;
1114 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1116 WepSet weapons_stat = WepSet_GetFromStat();
1117 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1118 int disownedcnt = 0;
1120 FOREACH(Weapons, it != WEP_Null, {
1121 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1123 WepSet set = it.m_wepset;
1124 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1126 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1133 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1134 if (weapon_cnt <= 0) return pos;
1137 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1139 int columnns = ceil(weapon_cnt / rows);
1141 float weapon_height = 29;
1142 float height = hud_fontsize.y + weapon_height;
1144 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1145 pos.y += 1.25 * hud_fontsize.y;
1146 if(panel.current_panel_bg != "0")
1147 pos.y += panel_bg_border;
1150 panel_size.y = height * rows;
1151 panel_size.y += panel_bg_padding * 2;
1154 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1155 if(panel.current_panel_bg != "0")
1156 end_pos.y += panel_bg_border * 2;
1158 if(panel_bg_padding)
1160 panel_pos += '1 1 0' * panel_bg_padding;
1161 panel_size -= '2 2 0' * panel_bg_padding;
1165 vector tmp = panel_size;
1167 float weapon_width = tmp.x / columnns / rows;
1170 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1174 // column highlighting
1175 for (int i = 0; i < columnns; ++i)
1177 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1180 for (int i = 0; i < rows; ++i)
1181 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * hud_fontsize.y, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1184 average_accuracy = 0;
1185 int weapons_with_stats = 0;
1187 pos.x += weapon_width / 2;
1189 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1192 Accuracy_LoadColors();
1194 float oldposx = pos.x;
1198 FOREACH(Weapons, it != WEP_Null, {
1199 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1201 WepSet set = it.m_wepset;
1202 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1206 if (weapon_stats >= 0)
1207 weapon_alpha = sbt_fg_alpha;
1209 weapon_alpha = 0.2 * sbt_fg_alpha;
1212 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1214 if (weapon_stats >= 0) {
1215 weapons_with_stats += 1;
1216 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1219 s = sprintf("%d%%", weapon_stats * 100);
1222 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1224 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1225 rgb = Accuracy_GetColor(weapon_stats);
1227 drawstring(tmpos + eX * padding + eY * weapon_height, s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1229 tmpos.x += weapon_width * rows;
1230 pos.x += weapon_width * rows;
1231 if (rows == 2 && column == columnns - 1) {
1239 if (weapons_with_stats)
1240 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1242 panel_size.x += panel_bg_padding * 2; // restore initial width
1246 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1248 pos.x += hud_fontsize.x * 0.25;
1249 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1250 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1251 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1253 pos.y += hud_fontsize.y;
1258 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1259 float stat_secrets_found, stat_secrets_total;
1260 float stat_monsters_killed, stat_monsters_total;
1264 // get monster stats
1265 stat_monsters_killed = STAT(MONSTERS_KILLED);
1266 stat_monsters_total = STAT(MONSTERS_TOTAL);
1268 // get secrets stats
1269 stat_secrets_found = STAT(SECRETS_FOUND);
1270 stat_secrets_total = STAT(SECRETS_TOTAL);
1272 // get number of rows
1273 if(stat_secrets_total)
1275 if(stat_monsters_total)
1278 // if no rows, return
1282 // draw table header
1283 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1284 pos.y += 1.25 * hud_fontsize.y;
1285 if(panel.current_panel_bg != "0")
1286 pos.y += panel_bg_border;
1289 panel_size.y = hud_fontsize.y * rows;
1290 panel_size.y += panel_bg_padding * 2;
1293 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1294 if(panel.current_panel_bg != "0")
1295 end_pos.y += panel_bg_border * 2;
1297 if(panel_bg_padding)
1299 panel_pos += '1 1 0' * panel_bg_padding;
1300 panel_size -= '2 2 0' * panel_bg_padding;
1304 vector tmp = panel_size;
1307 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1310 if(stat_monsters_total)
1312 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1313 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1317 if(stat_secrets_total)
1319 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1320 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1323 panel_size.x += panel_bg_padding * 2; // restore initial width
1328 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1331 RANKINGS_RECEIVED_CNT = 0;
1332 for (i=RANKINGS_CNT-1; i>=0; --i)
1334 ++RANKINGS_RECEIVED_CNT;
1336 if (RANKINGS_RECEIVED_CNT == 0)
1339 vector hl_rgb = rgb + '0.5 0.5 0.5';
1341 pos.y += hud_fontsize.y;
1342 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1343 pos.y += 1.25 * hud_fontsize.y;
1344 if(panel.current_panel_bg != "0")
1345 pos.y += panel_bg_border;
1350 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1352 float f = stringwidth(grecordholder[i], true, hud_fontsize);
1357 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1359 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1363 float ranksize = 3 * hud_fontsize.x;
1364 float timesize = 5 * hud_fontsize.x;
1365 vector columnsize = eX * (ranksize + timesize + namesize + hud_fontsize.x) + eY * 1.25 * hud_fontsize.y;
1366 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1367 columns = min(columns, RANKINGS_RECEIVED_CNT);
1369 // expand name column to fill the entire row
1370 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1371 namesize += available_space;
1372 columnsize.x += available_space;
1374 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1375 panel_size.y += panel_bg_padding * 2;
1379 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1380 if(panel.current_panel_bg != "0")
1381 end_pos.y += panel_bg_border * 2;
1383 if(panel_bg_padding)
1385 panel_pos += '1 1 0' * panel_bg_padding;
1386 panel_size -= '2 2 0' * panel_bg_padding;
1392 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1394 vector text_ofs = eX * 0.5 * hud_fontsize.x + eY * (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1396 int column = 0, j = 0;
1397 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1404 if(grecordholder[i] == entcs_GetName(player_localnum))
1405 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1406 else if(!((j + column) & 1) && sbt_highlight)
1407 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1409 str = count_ordinal(i+1);
1410 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1411 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1412 str = grecordholder[i];
1414 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1415 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1417 pos.y += 1.25 * hud_fontsize.y;
1419 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1423 pos.x += panel_size.x / columns;
1424 pos.y = panel_pos.y;
1428 panel_size.x += panel_bg_padding * 2; // restore initial width
1432 void Scoreboard_Draw()
1434 if(!autocvar__hud_configure)
1436 if(!hud_draw_maximized) return;
1438 // frametime checks allow to toggle the scoreboard even when the game is paused
1439 if(scoreboard_active) {
1440 if(hud_configure_menu_open == 1)
1441 scoreboard_fade_alpha = 1;
1442 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1443 if (scoreboard_fadeinspeed && frametime)
1444 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1446 scoreboard_fade_alpha = 1;
1447 if(hud_fontsize_str != autocvar_hud_fontsize)
1449 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1450 Scoreboard_initFieldSizes();
1451 if(hud_fontsize_str)
1452 strunzone(hud_fontsize_str);
1453 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1457 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1458 if (scoreboard_fadeoutspeed && frametime)
1459 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1461 scoreboard_fade_alpha = 0;
1464 if (!scoreboard_fade_alpha)
1468 scoreboard_fade_alpha = 0;
1470 if (autocvar_hud_panel_scoreboard_dynamichud)
1473 HUD_Scale_Disable();
1475 if(scoreboard_fade_alpha <= 0)
1477 panel_fade_alpha *= scoreboard_fade_alpha;
1478 HUD_Panel_LoadCvars();
1480 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1481 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1482 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1483 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1484 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1485 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1487 // don't overlap with con_notify
1488 if(!autocvar__hud_configure)
1489 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1491 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1492 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1493 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1494 panel_size.x = fixed_scoreboard_width;
1496 Scoreboard_UpdatePlayerTeams();
1498 vector pos = panel_pos;
1503 vector sb_heading_fontsize;
1504 sb_heading_fontsize = hud_fontsize * 2;
1505 draw_beginBoldFont();
1506 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1509 pos.y += sb_heading_fontsize.y;
1510 if(panel.current_panel_bg != "0")
1511 pos.y += panel_bg_border;
1513 // Draw the scoreboard
1514 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1517 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1521 vector panel_bg_color_save = panel_bg_color;
1522 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1523 if(panel.current_panel_bg != "0")
1524 team_score_baseoffset.x -= panel_bg_border;
1525 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1527 if(tm.team == NUM_SPECTATOR)
1532 draw_beginBoldFont();
1533 vector rgb = Team_ColorRGB(tm.team);
1534 str = ftos(tm.(teamscores(ts_primary)));
1535 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1537 if(ts_primary != ts_secondary)
1539 str = ftos(tm.(teamscores(ts_secondary)));
1540 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);
1543 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1544 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1545 else if(panel_bg_color_team > 0)
1546 panel_bg_color = rgb * panel_bg_color_team;
1548 panel_bg_color = rgb;
1549 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1551 panel_bg_color = panel_bg_color_save;
1555 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1556 if(tm.team != NUM_SPECTATOR)
1558 // display it anyway
1559 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1562 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1563 if(race_speedaward) {
1564 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);
1565 pos.y += 1.25 * hud_fontsize.y;
1567 if(race_speedaward_alltimebest) {
1568 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);
1569 pos.y += 1.25 * hud_fontsize.y;
1571 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1573 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1574 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1576 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1579 for(pl = players.sort_next; pl; pl = pl.sort_next)
1581 if(pl.team == NUM_SPECTATOR)
1583 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1584 if(tm.team == NUM_SPECTATOR)
1586 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1587 draw_beginBoldFont();
1588 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1590 pos.y += 1.25 * hud_fontsize.y;
1592 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1593 pos.y += 1.25 * hud_fontsize.y;
1599 // Print info string
1601 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1602 tl = STAT(TIMELIMIT);
1603 fl = STAT(FRAGLIMIT);
1604 ll = STAT(LEADLIMIT);
1605 if(gametype == MAPINFO_TYPE_LMS)
1608 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1613 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1617 str = strcat(str, _(" or"));
1620 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1621 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1622 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1623 TranslateScoresLabel(teamscores_label(ts_primary))));
1627 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1628 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1629 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1630 TranslateScoresLabel(scores_label(ps_primary))));
1635 if(tl > 0 || fl > 0)
1636 str = strcat(str, _(" or"));
1639 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1640 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1641 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1642 TranslateScoresLabel(teamscores_label(ts_primary))));
1646 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1647 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1648 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1649 TranslateScoresLabel(scores_label(ps_primary))));
1654 pos.y += 1.2 * hud_fontsize.y;
1655 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1657 // print information about respawn status
1658 float respawn_time = STAT(RESPAWN_TIME);
1662 if(respawn_time < 0)
1664 // a negative number means we are awaiting respawn, time value is still the same
1665 respawn_time *= -1; // remove mark now that we checked it
1667 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1668 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1670 str = sprintf(_("^1Respawning in ^3%s^1..."),
1671 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1672 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1674 count_seconds(ceil(respawn_time - time))
1678 else if(time < respawn_time)
1680 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1681 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1682 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1684 count_seconds(ceil(respawn_time - time))
1688 else if(time >= respawn_time)
1689 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1691 pos.y += 1.2 * hud_fontsize.y;
1692 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1695 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;