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 string autocvar_hud_fontsize;
14 string hud_fontsize_str;
19 float sbt_fg_alpha_self;
21 float sbt_highlight_alpha;
22 float sbt_highlight_alpha_self;
24 // provide basic panel cvars to old clients
25 // TODO remove them after a future release (0.8.2+)
26 string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
27 string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
28 string autocvar_hud_panel_scoreboard_bg = "border_default";
29 string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
30 string autocvar_hud_panel_scoreboard_bg_color_team = "";
31 string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
32 string autocvar_hud_panel_scoreboard_bg_border = "";
33 string autocvar_hud_panel_scoreboard_bg_padding = "";
35 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
36 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
37 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
38 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
39 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
40 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
41 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
42 bool autocvar_hud_panel_scoreboard_table_highlight = true;
43 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
44 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
45 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
46 float autocvar_hud_panel_scoreboard_namesize = 15;
48 bool autocvar_hud_panel_scoreboard_accuracy = true;
49 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
50 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
52 bool autocvar_hud_panel_scoreboard_dynamichud = false;
54 bool autocvar_hud_panel_scoreboard_maxrows = true;
55 int autocvar_hud_panel_scoreboard_maxrows_players = 20;
56 int autocvar_hud_panel_scoreboard_maxrows_teamplayers = 9;
57 bool autocvar_hud_panel_scoreboard_others_showscore = true;
60 void drawstringright(vector, string, vector, vector, float, float);
61 void drawstringcenter(vector, string, vector, vector, float, float);
63 // wrapper to put all possible scores titles through gettext
64 string TranslateScoresLabel(string l)
68 case "bckills": return CTX(_("SCO^bckills"));
69 case "bctime": return CTX(_("SCO^bctime"));
70 case "caps": return CTX(_("SCO^caps"));
71 case "captime": return CTX(_("SCO^captime"));
72 case "deaths": return CTX(_("SCO^deaths"));
73 case "destroyed": return CTX(_("SCO^destroyed"));
74 case "dmg": return CTX(_("SCO^damage"));
75 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
76 case "drops": return CTX(_("SCO^drops"));
77 case "faults": return CTX(_("SCO^faults"));
78 case "fckills": return CTX(_("SCO^fckills"));
79 case "goals": return CTX(_("SCO^goals"));
80 case "kckills": return CTX(_("SCO^kckills"));
81 case "kdratio": return CTX(_("SCO^kdratio"));
82 case "kd": return CTX(_("SCO^k/d"));
83 case "kdr": return CTX(_("SCO^kdr"));
84 case "kills": return CTX(_("SCO^kills"));
85 case "laps": return CTX(_("SCO^laps"));
86 case "lives": return CTX(_("SCO^lives"));
87 case "losses": return CTX(_("SCO^losses"));
88 case "name": return CTX(_("SCO^name"));
89 case "sum": return CTX(_("SCO^sum"));
90 case "nick": return CTX(_("SCO^nick"));
91 case "objectives": return CTX(_("SCO^objectives"));
92 case "pickups": return CTX(_("SCO^pickups"));
93 case "ping": return CTX(_("SCO^ping"));
94 case "pl": return CTX(_("SCO^pl"));
95 case "pushes": return CTX(_("SCO^pushes"));
96 case "rank": return CTX(_("SCO^rank"));
97 case "returns": return CTX(_("SCO^returns"));
98 case "revivals": return CTX(_("SCO^revivals"));
99 case "rounds": return CTX(_("SCO^rounds won"));
100 case "score": return CTX(_("SCO^score"));
101 case "suicides": return CTX(_("SCO^suicides"));
102 case "takes": return CTX(_("SCO^takes"));
103 case "ticks": return CTX(_("SCO^ticks"));
108 void Scoreboard_InitScores()
112 ps_primary = ps_secondary = NULL;
113 ts_primary = ts_secondary = -1;
114 FOREACH(Scores, true, {
115 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
116 if(f == SFL_SORT_PRIO_PRIMARY)
118 if(f == SFL_SORT_PRIO_SECONDARY)
121 if(ps_secondary == NULL)
122 ps_secondary = ps_primary;
124 for(i = 0; i < MAX_TEAMSCORE; ++i)
126 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
127 if(f == SFL_SORT_PRIO_PRIMARY)
129 if(f == SFL_SORT_PRIO_SECONDARY)
132 if(ts_secondary == -1)
133 ts_secondary = ts_primary;
135 Cmd_Scoreboard_SetFields(0);
138 float SetTeam(entity pl, float Team);
140 void Scoreboard_UpdatePlayerTeams()
147 for(pl = players.sort_next; pl; pl = pl.sort_next)
150 Team = entcs_GetScoreTeam(pl.sv_entnum);
151 if(SetTeam(pl, Team))
154 Scoreboard_UpdatePlayerPos(pl);
158 pl = players.sort_next;
163 print(strcat("PNUM: ", ftos(num), "\n"));
168 int Scoreboard_CompareScore(int vl, int vr, int f)
170 TC(int, vl); TC(int, vr); TC(int, f);
171 if(f & SFL_ZERO_IS_WORST)
173 if(vl == 0 && vr != 0)
175 if(vl != 0 && vr == 0)
179 return IS_INCREASING(f);
181 return IS_DECREASING(f);
185 float Scoreboard_ComparePlayerScores(entity left, entity right)
188 vl = entcs_GetTeam(left.sv_entnum);
189 vr = entcs_GetTeam(right.sv_entnum);
201 if(vl == NUM_SPECTATOR)
203 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
205 if(!left.gotscores && right.gotscores)
210 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
214 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
218 FOREACH(Scores, true, {
219 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
220 if (r >= 0) return r;
223 if (left.sv_entnum < right.sv_entnum)
229 void Scoreboard_UpdatePlayerPos(entity player)
232 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
234 SORT_SWAP(player, ent);
236 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
238 SORT_SWAP(ent, player);
242 float Scoreboard_CompareTeamScores(entity left, entity right)
246 if(left.team == NUM_SPECTATOR)
248 if(right.team == NUM_SPECTATOR)
251 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
255 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
259 for(i = 0; i < MAX_TEAMSCORE; ++i)
261 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
266 if (left.team < right.team)
272 void Scoreboard_UpdateTeamPos(entity Team)
275 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
277 SORT_SWAP(Team, ent);
279 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
281 SORT_SWAP(ent, Team);
285 void Cmd_Scoreboard_Help()
287 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
288 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
289 LOG_INFO(_("Usage:\n"));
290 LOG_INFO(_("^2scoreboard_columns_set default\n"));
291 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
292 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
293 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
296 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
297 LOG_INFO(_("^3ping^7 Ping time\n"));
298 LOG_INFO(_("^3pl^7 Packet loss\n"));
299 LOG_INFO(_("^3elo^7 Player ELO\n"));
300 LOG_INFO(_("^3kills^7 Number of kills\n"));
301 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
302 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
303 LOG_INFO(_("^3frags^7 kills - suicides\n"));
304 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
305 LOG_INFO(_("^3dmg^7 The total damage done\n"));
306 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
307 LOG_INFO(_("^3sum^7 frags - deaths\n"));
308 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
309 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
310 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
311 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
312 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
313 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
314 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
315 LOG_INFO(_("^3rank^7 Player rank\n"));
316 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
317 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
318 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
319 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
320 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
321 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
322 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
323 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
324 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
325 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
326 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
327 LOG_INFO(_("^3score^7 Total score\n"));
330 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
331 "of game types, then a slash, to make the field show up only in these\n"
332 "or in all but these game types. You can also specify 'all' as a\n"
333 "field to show all fields available for the current game mode.\n\n"));
335 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
336 "include/exclude ALL teams/noteams game modes.\n\n"));
338 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
339 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
340 "right of the vertical bar aligned to the right.\n"));
341 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
342 "other gamemodes except DM.\n"));
345 // NOTE: adding a gametype with ? to not warn for an optional field
346 // make sure it's excluded in a previous exclusive rule, if any
347 // otherwise the previous exclusive rule warns anyway
348 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
349 #define SCOREBOARD_DEFAULT_COLUMNS \
351 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
352 " -teams,lms/deaths +ft,tdm/deaths" \
353 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
354 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
355 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
356 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
357 " +lms/lives +lms/rank" \
358 " +kh/caps +kh/pushes +kh/destroyed" \
359 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
360 " +as/objectives +nb/faults +nb/goals" \
361 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
362 " -lms,rc,cts,inv,nb/score"
364 void Cmd_Scoreboard_SetFields(int argc)
369 float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
373 return; // do nothing, we don't know gametype and scores yet
375 // sbt_fields uses strunzone on the titles!
376 if(!sbt_field_title[0])
377 for(i = 0; i < MAX_SBT_FIELDS; ++i)
378 sbt_field_title[i] = strzone("(null)");
380 // TODO: re enable with gametype dependant cvars?
381 if(argc < 3) // no arguments provided
382 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
385 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
389 if(argv(2) == "default")
390 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
391 else if(argv(2) == "all")
394 s = "ping pl name |";
395 FOREACH(Scores, true, {
397 if(it != ps_secondary)
398 if(scores_label(it) != "")
399 s = strcat(s, " ", scores_label(it));
401 if(ps_secondary != ps_primary)
402 s = strcat(s, " ", scores_label(ps_secondary));
403 s = strcat(s, " ", scores_label(ps_primary));
404 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
411 hud_fontsize = HUD_GetFontsize("hud_fontsize");
413 for(i = 1; i < argc - 1; ++i)
419 if(substring(str, 0, 1) == "?")
422 str = substring(str, 1, strlen(str) - 1);
425 slash = strstrofs(str, "/", 0);
428 pattern = substring(str, 0, slash);
429 str = substring(str, slash + 1, strlen(str) - (slash + 1));
431 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
435 strunzone(sbt_field_title[sbt_num_fields]);
436 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
437 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
438 str = strtolower(str);
443 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
444 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
445 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
446 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
447 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
448 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
449 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
450 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
451 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
454 FOREACH(Scores, true, {
455 if (str == strtolower(scores_label(it))) {
457 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
467 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
471 sbt_field[sbt_num_fields] = j;
474 if(j == ps_secondary)
480 if(sbt_num_fields >= MAX_SBT_FIELDS)
484 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
486 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
488 if(ps_primary == ps_secondary)
490 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
492 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
496 strunzone(sbt_field_title[sbt_num_fields]);
497 for(i = sbt_num_fields; i > 0; --i)
499 sbt_field_title[i] = sbt_field_title[i-1];
500 sbt_field_size[i] = sbt_field_size[i-1];
501 sbt_field[i] = sbt_field[i-1];
503 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
504 sbt_field[0] = SP_NAME;
506 LOG_INFO("fixed missing field 'name'\n");
510 strunzone(sbt_field_title[sbt_num_fields]);
511 for(i = sbt_num_fields; i > 1; --i)
513 sbt_field_title[i] = sbt_field_title[i-1];
514 sbt_field_size[i] = sbt_field_size[i-1];
515 sbt_field[i] = sbt_field[i-1];
517 sbt_field_title[1] = strzone("|");
518 sbt_field[1] = SP_SEPARATOR;
519 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
521 LOG_INFO("fixed missing field '|'\n");
524 else if(!have_separator)
526 strunzone(sbt_field_title[sbt_num_fields]);
527 sbt_field_title[sbt_num_fields] = strzone("|");
528 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
529 sbt_field[sbt_num_fields] = SP_SEPARATOR;
531 LOG_INFO("fixed missing field '|'\n");
535 strunzone(sbt_field_title[sbt_num_fields]);
536 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
537 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
538 sbt_field[sbt_num_fields] = ps_secondary;
540 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
544 strunzone(sbt_field_title[sbt_num_fields]);
545 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
546 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
547 sbt_field[sbt_num_fields] = ps_primary;
549 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
553 sbt_field[sbt_num_fields] = SP_END;
557 vector sbt_field_rgb;
558 string sbt_field_icon0;
559 string sbt_field_icon1;
560 string sbt_field_icon2;
561 vector sbt_field_icon0_rgb;
562 vector sbt_field_icon1_rgb;
563 vector sbt_field_icon2_rgb;
564 float sbt_field_icon0_alpha;
565 float sbt_field_icon1_alpha;
566 float sbt_field_icon2_alpha;
567 string Scoreboard_GetField(entity pl, PlayerScoreField field)
569 float tmp, num, denom;
572 sbt_field_rgb = '1 1 1';
573 sbt_field_icon0 = "";
574 sbt_field_icon1 = "";
575 sbt_field_icon2 = "";
576 sbt_field_icon0_rgb = '1 1 1';
577 sbt_field_icon1_rgb = '1 1 1';
578 sbt_field_icon2_rgb = '1 1 1';
579 sbt_field_icon0_alpha = 1;
580 sbt_field_icon1_alpha = 1;
581 sbt_field_icon2_alpha = 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)
760 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
763 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
766 vector column_dim = eY * panel_size.y;
768 column_dim.y -= 1.25 * hud_fontsize.y;
769 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
770 pos.x += hud_fontsize.x * 0.5;
771 for(i = 0; i < sbt_num_fields; ++i)
773 if(sbt_field[i] == SP_SEPARATOR)
775 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
778 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
779 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
780 pos.x += column_dim.x;
782 if(sbt_field[i] == SP_SEPARATOR)
784 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
785 for(i = sbt_num_fields - 1; i > 0; --i)
787 if(sbt_field[i] == SP_SEPARATOR)
790 pos.x -= sbt_field_size[i];
795 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
796 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
799 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
800 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
801 pos.x -= hud_fontsize.x;
806 pos.y += 1.25 * hud_fontsize.y;
810 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
812 TC(bool, is_self); TC(int, pl_number);
814 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
816 vector h_pos = item_pos;
817 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
818 // alternated rows highlighting
820 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
821 else if((sbt_highlight) && (!(pl_number % 2)))
822 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
824 vector pos = item_pos;
825 pos.x += hud_fontsize.x * 0.5;
826 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
827 vector tmp = '0 0 0';
829 PlayerScoreField field;
830 for(i = 0; i < sbt_num_fields; ++i)
832 field = sbt_field[i];
833 if(field == SP_SEPARATOR)
836 if(is_spec && field != SP_NAME && field != SP_PING) {
837 pos.x += sbt_field_size[i] + hud_fontsize.x;
840 str = Scoreboard_GetField(pl, field);
841 str = Scoreboard_FixColumnWidth(i, str);
843 pos.x += sbt_field_size[i] + hud_fontsize.x;
845 if(field == SP_NAME) {
846 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
848 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
850 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
852 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
854 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
856 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
859 tmp.x = sbt_field_size[i] + hud_fontsize.x;
860 if(sbt_field_icon0 != "")
862 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
864 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
865 if(sbt_field_icon1 != "")
867 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
869 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
870 if(sbt_field_icon2 != "")
872 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
874 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
877 if(sbt_field[i] == SP_SEPARATOR)
879 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
880 for(i = sbt_num_fields-1; i > 0; --i)
882 field = sbt_field[i];
883 if(field == SP_SEPARATOR)
886 if(is_spec && field != SP_NAME && field != SP_PING) {
887 pos.x -= sbt_field_size[i] + hud_fontsize.x;
891 str = Scoreboard_GetField(pl, field);
892 str = Scoreboard_FixColumnWidth(i, str);
894 if(field == SP_NAME) {
895 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
897 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
899 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
901 tmp.x = sbt_fixcolumnwidth_len;
903 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
905 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
908 tmp.x = sbt_field_size[i];
909 if(sbt_field_icon0 != "")
911 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
913 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
914 if(sbt_field_icon1 != "")
916 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
918 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
919 if(sbt_field_icon2 != "")
921 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
923 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
924 pos.x -= sbt_field_size[i] + hud_fontsize.x;
929 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
932 void Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
934 vector h_pos = item_pos;
935 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
936 if((sbt_highlight) && (!(pl_number % 2)))
937 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
939 vector pos = item_pos;
940 pos.x += hud_fontsize.x * 0.5;
941 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
943 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
944 width_limit -= stringwidth("...", false, hud_fontsize);
945 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
946 for(int i = 0; pl; pl = pl.sort_next)
948 if(pl.team != this_team)
952 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
953 if(autocvar_hud_panel_scoreboard_others_showscore)
954 str = sprintf("%s ^7(^3%s^7)", str, ftos(pl.(scores(ps_primary))));
955 float str_width = stringwidth(str, true, hud_fontsize);
956 if(pos.x + str_width > width_limit)
958 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
961 drawcolorcodedstring(pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
962 pos.x += str_width + hud_fontsize.x * 0.5;
967 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
969 int max_players = 999;
970 if(autocvar_hud_panel_scoreboard_maxrows)
973 max_players = autocvar_hud_panel_scoreboard_maxrows_teamplayers;
975 max_players = autocvar_hud_panel_scoreboard_maxrows_players;
978 if(max_players == tm.team_size)
983 entity me = playerslots[current_player];
985 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
986 panel_size.y += panel_bg_padding * 2;
989 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
990 if(panel.current_panel_bg != "0")
991 end_pos.y += panel_bg_border * 2;
995 panel_pos += '1 1 0' * panel_bg_padding;
996 panel_size -= '2 2 0' * panel_bg_padding;
1000 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
1004 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1006 pos.y += 1.25 * hud_fontsize.y;
1009 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1011 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1014 // print header row and highlight columns
1015 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1017 // fill the table and draw the rows
1018 bool is_self = false;
1019 bool self_shown = false;
1021 for(pl = players.sort_next; pl; pl = pl.sort_next)
1023 if(pl.team != tm.team)
1025 if(i == max_players - 2 && pl != me)
1027 if(!self_shown && me.team == tm.team)
1029 Scoreboard_DrawItem(pos, rgb, me, true, i);
1031 pos.y += 1.25 * hud_fontsize.y;
1035 if(i >= max_players - 1)
1037 Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1038 pos.y += 1.25 * hud_fontsize.y;
1041 is_self = (pl.sv_entnum == current_player);
1042 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1045 pos.y += 1.25 * hud_fontsize.y;
1049 panel_size.x += panel_bg_padding * 2; // restore initial width
1053 float Scoreboard_WouldDraw() {
1054 if (QuickMenu_IsOpened())
1056 else if (HUD_Radar_Clickable())
1058 else if (scoreboard_showscores)
1060 else if (intermission == 1)
1062 else if (intermission == 2)
1064 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1066 else if (scoreboard_showscores_force)
1071 float average_accuracy;
1072 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1074 WepSet weapons_stat = WepSet_GetFromStat();
1075 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1076 int disownedcnt = 0;
1078 FOREACH(Weapons, it != WEP_Null, {
1079 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1081 WepSet set = it.m_wepset;
1082 if (weapon_stats < 0)
1084 if (!(weapons_stat & set) && (it.spawnflags & WEP_FLAG_HIDDEN || it.spawnflags & WEP_FLAG_MUTATORBLOCKED))
1086 else if (!(weapons_stat & set || weapons_inmap & set))
1091 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1092 if (weapon_cnt <= 0) return pos;
1095 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1097 int columnns = ceil(weapon_cnt / rows);
1099 float weapon_height = 29;
1100 float height = hud_fontsize.y + weapon_height;
1102 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1103 pos.y += 1.25 * hud_fontsize.y;
1104 if(panel.current_panel_bg != "0")
1105 pos.y += panel_bg_border;
1108 panel_size.y = height * rows;
1109 panel_size.y += panel_bg_padding * 2;
1112 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1113 if(panel.current_panel_bg != "0")
1114 end_pos.y += panel_bg_border * 2;
1116 if(panel_bg_padding)
1118 panel_pos += '1 1 0' * panel_bg_padding;
1119 panel_size -= '2 2 0' * panel_bg_padding;
1123 vector tmp = panel_size;
1125 float weapon_width = tmp.x / columnns / rows;
1128 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1132 // column highlighting
1133 for (int i = 0; i < columnns; ++i)
1135 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1138 for (int i = 0; i < rows; ++i)
1139 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * hud_fontsize.y, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1142 average_accuracy = 0;
1143 int weapons_with_stats = 0;
1145 pos.x += weapon_width / 2;
1147 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1150 Accuracy_LoadColors();
1152 float oldposx = pos.x;
1156 FOREACH(Weapons, it != WEP_Null, {
1157 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1159 WepSet set = it.m_wepset;
1160 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1164 if (weapon_stats >= 0)
1165 weapon_alpha = sbt_fg_alpha;
1167 weapon_alpha = 0.2 * sbt_fg_alpha;
1170 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1172 if (weapon_stats >= 0) {
1173 weapons_with_stats += 1;
1174 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1177 s = sprintf("%d%%", weapon_stats * 100);
1180 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1182 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1183 rgb = Accuracy_GetColor(weapon_stats);
1185 drawstring(tmpos + eX * padding + eY * weapon_height, s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1187 tmpos.x += weapon_width * rows;
1188 pos.x += weapon_width * rows;
1189 if (rows == 2 && column == columnns - 1) {
1197 if (weapons_with_stats)
1198 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1200 panel_size.x += panel_bg_padding * 2; // restore initial width
1204 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1206 pos.x += hud_fontsize.x * 0.25;
1207 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1208 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1209 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1211 pos.y += hud_fontsize.y;
1216 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1217 float stat_secrets_found, stat_secrets_total;
1218 float stat_monsters_killed, stat_monsters_total;
1222 // get monster stats
1223 stat_monsters_killed = STAT(MONSTERS_KILLED);
1224 stat_monsters_total = STAT(MONSTERS_TOTAL);
1226 // get secrets stats
1227 stat_secrets_found = STAT(SECRETS_FOUND);
1228 stat_secrets_total = STAT(SECRETS_TOTAL);
1230 // get number of rows
1231 if(stat_secrets_total)
1233 if(stat_monsters_total)
1236 // if no rows, return
1240 // draw table header
1241 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1242 pos.y += 1.25 * hud_fontsize.y;
1243 if(panel.current_panel_bg != "0")
1244 pos.y += panel_bg_border;
1247 panel_size.y = hud_fontsize.y * rows;
1248 panel_size.y += panel_bg_padding * 2;
1251 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1252 if(panel.current_panel_bg != "0")
1253 end_pos.y += panel_bg_border * 2;
1255 if(panel_bg_padding)
1257 panel_pos += '1 1 0' * panel_bg_padding;
1258 panel_size -= '2 2 0' * panel_bg_padding;
1262 vector tmp = panel_size;
1265 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1268 if(stat_monsters_total)
1270 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1271 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1275 if(stat_secrets_total)
1277 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1278 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1281 panel_size.x += panel_bg_padding * 2; // restore initial width
1286 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1289 RANKINGS_RECEIVED_CNT = 0;
1290 for (i=RANKINGS_CNT-1; i>=0; --i)
1292 ++RANKINGS_RECEIVED_CNT;
1294 if (RANKINGS_RECEIVED_CNT == 0)
1297 vector hl_rgb = rgb + '0.5 0.5 0.5';
1299 pos.y += hud_fontsize.y;
1300 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1301 pos.y += 1.25 * hud_fontsize.y;
1302 if(panel.current_panel_bg != "0")
1303 pos.y += panel_bg_border;
1306 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1307 panel_size.y += panel_bg_padding * 2;
1310 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1311 if(panel.current_panel_bg != "0")
1312 end_pos.y += panel_bg_border * 2;
1314 if(panel_bg_padding)
1316 panel_pos += '1 1 0' * panel_bg_padding;
1317 panel_size -= '2 2 0' * panel_bg_padding;
1321 vector tmp = panel_size;
1324 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1327 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1334 n = grecordholder[i];
1335 p = count_ordinal(i+1);
1336 if(grecordholder[i] == entcs_GetName(player_localnum))
1337 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1338 else if(!(i % 2) && sbt_highlight)
1339 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1340 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1341 drawstring(pos + '3 0 0' * hud_fontsize.y, TIME_ENCODED_TOSTRING(t), '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1342 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1343 pos.y += 1.25 * hud_fontsize.y;
1346 panel_size.x += panel_bg_padding * 2; // restore initial width
1350 void Scoreboard_Draw()
1352 if(!autocvar__hud_configure)
1354 if(!hud_draw_maximized) return;
1356 // frametime checks allow to toggle the scoreboard even when the game is paused
1357 if(scoreboard_active) {
1358 if(hud_configure_menu_open == 1)
1359 scoreboard_fade_alpha = 1;
1360 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1361 if (scoreboard_fadeinspeed && frametime)
1362 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1364 scoreboard_fade_alpha = 1;
1365 if(hud_fontsize_str != autocvar_hud_fontsize)
1367 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1368 Scoreboard_initFieldSizes();
1369 if(hud_fontsize_str)
1370 strunzone(hud_fontsize_str);
1371 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1375 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1376 if (scoreboard_fadeoutspeed && frametime)
1377 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1379 scoreboard_fade_alpha = 0;
1382 if (!scoreboard_fade_alpha)
1386 scoreboard_fade_alpha = 0;
1388 if (autocvar_hud_panel_scoreboard_dynamichud)
1391 HUD_Scale_Disable();
1393 if(scoreboard_fade_alpha <= 0)
1395 panel_fade_alpha *= scoreboard_fade_alpha;
1396 HUD_Panel_LoadCvars();
1398 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1399 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1400 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1401 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1402 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1403 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1405 // don't overlap with con_notify
1406 if(!autocvar__hud_configure)
1407 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1409 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1410 float fixed_scoreboard_width = bound(vid_conwidth * 0.4, vid_conwidth - excess, vid_conwidth * 0.93);
1411 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1412 panel_size.x = fixed_scoreboard_width;
1414 Scoreboard_UpdatePlayerTeams();
1420 // Initializes position
1424 vector sb_heading_fontsize;
1425 sb_heading_fontsize = hud_fontsize * 2;
1426 draw_beginBoldFont();
1427 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1430 pos.y += sb_heading_fontsize.y;
1431 if(panel.current_panel_bg != "0")
1432 pos.y += panel_bg_border;
1434 // Draw the scoreboard
1435 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1438 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1442 vector panel_bg_color_save = panel_bg_color;
1443 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1444 if(panel.current_panel_bg != "0")
1445 team_score_baseoffset.x -= panel_bg_border;
1446 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1448 if(tm.team == NUM_SPECTATOR)
1453 draw_beginBoldFont();
1454 vector rgb = Team_ColorRGB(tm.team);
1455 str = ftos(tm.(teamscores(ts_primary)));
1456 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1458 if(ts_primary != ts_secondary)
1460 str = ftos(tm.(teamscores(ts_secondary)));
1461 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);
1464 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1465 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1466 else if(panel_bg_color_team > 0)
1467 panel_bg_color = rgb * panel_bg_color_team;
1469 panel_bg_color = rgb;
1470 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1472 panel_bg_color = panel_bg_color_save;
1476 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1478 if(tm.team == NUM_SPECTATOR)
1481 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1485 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1486 if(race_speedaward) {
1487 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);
1488 pos.y += 1.25 * hud_fontsize.y;
1490 if(race_speedaward_alltimebest) {
1491 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);
1492 pos.y += 1.25 * hud_fontsize.y;
1494 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1496 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1497 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1499 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1504 for(pl = players.sort_next; pl; pl = pl.sort_next)
1506 if(pl.team != NUM_SPECTATOR)
1508 pos.y += 1.25 * hud_fontsize.y;
1509 Scoreboard_DrawItem(pos, '0 0 0', pl, (pl.sv_entnum == player_localnum), specs);
1515 draw_beginBoldFont();
1516 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1518 pos.y += 1.25 * hud_fontsize.y;
1521 // Print info string
1523 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1524 tl = STAT(TIMELIMIT);
1525 fl = STAT(FRAGLIMIT);
1526 ll = STAT(LEADLIMIT);
1527 if(gametype == MAPINFO_TYPE_LMS)
1530 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1535 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1539 str = strcat(str, _(" or"));
1542 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1543 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1544 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1545 TranslateScoresLabel(teamscores_label(ts_primary))));
1549 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1550 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1551 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1552 TranslateScoresLabel(scores_label(ps_primary))));
1557 if(tl > 0 || fl > 0)
1558 str = strcat(str, _(" or"));
1561 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1562 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1563 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1564 TranslateScoresLabel(teamscores_label(ts_primary))));
1568 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1569 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1570 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1571 TranslateScoresLabel(scores_label(ps_primary))));
1576 pos.y += 1.2 * hud_fontsize.y;
1577 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1579 // print information about respawn status
1580 float respawn_time = STAT(RESPAWN_TIME);
1584 if(respawn_time < 0)
1586 // a negative number means we are awaiting respawn, time value is still the same
1587 respawn_time *= -1; // remove mark now that we checked it
1589 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1590 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1592 str = sprintf(_("^1Respawning in ^3%s^1..."),
1593 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1594 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1596 count_seconds(ceil(respawn_time - time))
1600 else if(time < respawn_time)
1602 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1603 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1604 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1606 count_seconds(ceil(respawn_time - time))
1610 else if(time >= respawn_time)
1611 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1613 pos.y += 1.2 * hud_fontsize.y;
1614 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1617 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;