1 #include "scoreboard.qh"
3 #include "quickmenu.qh"
4 #include <common/ent_cs.qh>
5 #include <common/constants.qh>
6 #include <common/mapinfo.qh>
7 #include <common/minigames/cl_minigames.qh>
8 #include <common/stats.qh>
9 #include <common/teams.qh>
13 const int MAX_SBT_FIELDS = MAX_SCORE;
15 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
16 float sbt_field_size[MAX_SBT_FIELDS + 1];
17 string sbt_field_title[MAX_SBT_FIELDS + 1];
20 string autocvar_hud_fontsize;
21 string hud_fontsize_str;
26 float sbt_fg_alpha_self;
28 float sbt_highlight_alpha;
29 float sbt_highlight_alpha_self;
31 // provide basic panel cvars to old clients
32 // TODO remove them after a future release (0.8.2+)
33 string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
34 string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
35 string autocvar_hud_panel_scoreboard_bg = "border_default";
36 string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
37 string autocvar_hud_panel_scoreboard_bg_color_team = "";
38 string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
39 string autocvar_hud_panel_scoreboard_bg_border = "";
40 string autocvar_hud_panel_scoreboard_bg_padding = "";
42 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
43 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
44 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
45 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
46 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
47 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
48 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
49 bool autocvar_hud_panel_scoreboard_table_highlight = true;
50 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
51 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
52 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
53 float autocvar_hud_panel_scoreboard_namesize = 15;
55 bool autocvar_hud_panel_scoreboard_accuracy = true;
56 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
57 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
59 bool autocvar_hud_panel_scoreboard_dynamichud = false;
61 bool autocvar_hud_panel_scoreboard_maxrows = true;
62 int autocvar_hud_panel_scoreboard_maxrows_players = 20;
63 int autocvar_hud_panel_scoreboard_maxrows_teamplayers = 9;
64 bool autocvar_hud_panel_scoreboard_others_showscore = true;
65 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
68 void drawstringright(vector, string, vector, vector, float, float);
69 void drawstringcenter(vector, string, vector, vector, float, float);
71 // wrapper to put all possible scores titles through gettext
72 string TranslateScoresLabel(string l)
76 case "bckills": return CTX(_("SCO^bckills"));
77 case "bctime": return CTX(_("SCO^bctime"));
78 case "caps": return CTX(_("SCO^caps"));
79 case "captime": return CTX(_("SCO^captime"));
80 case "deaths": return CTX(_("SCO^deaths"));
81 case "destroyed": return CTX(_("SCO^destroyed"));
82 case "dmg": return CTX(_("SCO^damage"));
83 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
84 case "drops": return CTX(_("SCO^drops"));
85 case "faults": return CTX(_("SCO^faults"));
86 case "fckills": return CTX(_("SCO^fckills"));
87 case "goals": return CTX(_("SCO^goals"));
88 case "kckills": return CTX(_("SCO^kckills"));
89 case "kdratio": return CTX(_("SCO^kdratio"));
90 case "kd": return CTX(_("SCO^k/d"));
91 case "kdr": return CTX(_("SCO^kdr"));
92 case "kills": return CTX(_("SCO^kills"));
93 case "laps": return CTX(_("SCO^laps"));
94 case "lives": return CTX(_("SCO^lives"));
95 case "losses": return CTX(_("SCO^losses"));
96 case "name": return CTX(_("SCO^name"));
97 case "sum": return CTX(_("SCO^sum"));
98 case "nick": return CTX(_("SCO^nick"));
99 case "objectives": return CTX(_("SCO^objectives"));
100 case "pickups": return CTX(_("SCO^pickups"));
101 case "ping": return CTX(_("SCO^ping"));
102 case "pl": return CTX(_("SCO^pl"));
103 case "pushes": return CTX(_("SCO^pushes"));
104 case "rank": return CTX(_("SCO^rank"));
105 case "returns": return CTX(_("SCO^returns"));
106 case "revivals": return CTX(_("SCO^revivals"));
107 case "rounds": return CTX(_("SCO^rounds won"));
108 case "score": return CTX(_("SCO^score"));
109 case "suicides": return CTX(_("SCO^suicides"));
110 case "takes": return CTX(_("SCO^takes"));
111 case "ticks": return CTX(_("SCO^ticks"));
116 void Scoreboard_InitScores()
120 ps_primary = ps_secondary = NULL;
121 ts_primary = ts_secondary = -1;
122 FOREACH(Scores, true, {
123 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
124 if(f == SFL_SORT_PRIO_PRIMARY)
126 if(f == SFL_SORT_PRIO_SECONDARY)
129 if(ps_secondary == NULL)
130 ps_secondary = ps_primary;
132 for(i = 0; i < MAX_TEAMSCORE; ++i)
134 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
135 if(f == SFL_SORT_PRIO_PRIMARY)
137 if(f == SFL_SORT_PRIO_SECONDARY)
140 if(ts_secondary == -1)
141 ts_secondary = ts_primary;
143 Cmd_Scoreboard_SetFields(0);
146 float SetTeam(entity pl, float Team);
148 void Scoreboard_UpdatePlayerTeams()
155 for(pl = players.sort_next; pl; pl = pl.sort_next)
158 Team = entcs_GetScoreTeam(pl.sv_entnum);
159 if(SetTeam(pl, Team))
162 Scoreboard_UpdatePlayerPos(pl);
166 pl = players.sort_next;
171 print(strcat("PNUM: ", ftos(num), "\n"));
176 int Scoreboard_CompareScore(int vl, int vr, int f)
178 TC(int, vl); TC(int, vr); TC(int, f);
179 if(f & SFL_ZERO_IS_WORST)
181 if(vl == 0 && vr != 0)
183 if(vl != 0 && vr == 0)
187 return IS_INCREASING(f);
189 return IS_DECREASING(f);
193 float Scoreboard_ComparePlayerScores(entity left, entity right)
196 vl = entcs_GetTeam(left.sv_entnum);
197 vr = entcs_GetTeam(right.sv_entnum);
209 if(vl == NUM_SPECTATOR)
211 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
213 if(!left.gotscores && right.gotscores)
218 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
222 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
226 FOREACH(Scores, true, {
227 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
228 if (r >= 0) return r;
231 if (left.sv_entnum < right.sv_entnum)
237 void Scoreboard_UpdatePlayerPos(entity player)
240 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
242 SORT_SWAP(player, ent);
244 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
246 SORT_SWAP(ent, player);
250 float Scoreboard_CompareTeamScores(entity left, entity right)
254 if(left.team == NUM_SPECTATOR)
256 if(right.team == NUM_SPECTATOR)
259 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
263 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
267 for(i = 0; i < MAX_TEAMSCORE; ++i)
269 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
274 if (left.team < right.team)
280 void Scoreboard_UpdateTeamPos(entity Team)
283 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
285 SORT_SWAP(Team, ent);
287 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
289 SORT_SWAP(ent, Team);
293 void Cmd_Scoreboard_Help()
295 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
296 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
297 LOG_INFO(_("Usage:\n"));
298 LOG_INFO(_("^2scoreboard_columns_set default\n"));
299 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
300 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
301 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
304 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
305 LOG_INFO(_("^3ping^7 Ping time\n"));
306 LOG_INFO(_("^3pl^7 Packet loss\n"));
307 LOG_INFO(_("^3elo^7 Player ELO\n"));
308 LOG_INFO(_("^3kills^7 Number of kills\n"));
309 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
310 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
311 LOG_INFO(_("^3frags^7 kills - suicides\n"));
312 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
313 LOG_INFO(_("^3dmg^7 The total damage done\n"));
314 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
315 LOG_INFO(_("^3sum^7 frags - deaths\n"));
316 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
317 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
318 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
319 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
320 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
321 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
322 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
323 LOG_INFO(_("^3rank^7 Player rank\n"));
324 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
325 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
326 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
327 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
328 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
329 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
330 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
331 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
332 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
333 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
334 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
335 LOG_INFO(_("^3score^7 Total score\n"));
338 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
339 "of game types, then a slash, to make the field show up only in these\n"
340 "or in all but these game types. You can also specify 'all' as a\n"
341 "field to show all fields available for the current game mode.\n\n"));
343 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
344 "include/exclude ALL teams/noteams game modes.\n\n"));
346 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
347 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
348 "right of the vertical bar aligned to the right.\n"));
349 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
350 "other gamemodes except DM.\n"));
353 // NOTE: adding a gametype with ? to not warn for an optional field
354 // make sure it's excluded in a previous exclusive rule, if any
355 // otherwise the previous exclusive rule warns anyway
356 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
357 #define SCOREBOARD_DEFAULT_COLUMNS \
359 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
360 " -teams,lms/deaths +ft,tdm/deaths" \
361 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
362 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
363 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
364 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
365 " +lms/lives +lms/rank" \
366 " +kh/caps +kh/pushes +kh/destroyed" \
367 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
368 " +as/objectives +nb/faults +nb/goals" \
369 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
370 " -lms,rc,cts,inv,nb/score"
372 void Cmd_Scoreboard_SetFields(int argc)
377 float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
381 return; // do nothing, we don't know gametype and scores yet
383 // sbt_fields uses strunzone on the titles!
384 if(!sbt_field_title[0])
385 for(i = 0; i < MAX_SBT_FIELDS; ++i)
386 sbt_field_title[i] = strzone("(null)");
388 // TODO: re enable with gametype dependant cvars?
389 if(argc < 3) // no arguments provided
390 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
393 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
397 if(argv(2) == "default")
398 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
399 else if(argv(2) == "all")
402 s = "ping pl name |";
403 FOREACH(Scores, true, {
405 if(it != ps_secondary)
406 if(scores_label(it) != "")
407 s = strcat(s, " ", scores_label(it));
409 if(ps_secondary != ps_primary)
410 s = strcat(s, " ", scores_label(ps_secondary));
411 s = strcat(s, " ", scores_label(ps_primary));
412 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
419 hud_fontsize = HUD_GetFontsize("hud_fontsize");
421 for(i = 1; i < argc - 1; ++i)
427 if(substring(str, 0, 1) == "?")
430 str = substring(str, 1, strlen(str) - 1);
433 slash = strstrofs(str, "/", 0);
436 pattern = substring(str, 0, slash);
437 str = substring(str, slash + 1, strlen(str) - (slash + 1));
439 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
443 strunzone(sbt_field_title[sbt_num_fields]);
444 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
445 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
446 str = strtolower(str);
451 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
452 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
453 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
454 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
455 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
456 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
457 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
458 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
459 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
462 FOREACH(Scores, true, {
463 if (str == strtolower(scores_label(it))) {
465 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
475 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
479 sbt_field[sbt_num_fields] = j;
482 if(j == ps_secondary)
488 if(sbt_num_fields >= MAX_SBT_FIELDS)
492 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
494 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
496 if(ps_primary == ps_secondary)
498 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
500 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
504 strunzone(sbt_field_title[sbt_num_fields]);
505 for(i = sbt_num_fields; i > 0; --i)
507 sbt_field_title[i] = sbt_field_title[i-1];
508 sbt_field_size[i] = sbt_field_size[i-1];
509 sbt_field[i] = sbt_field[i-1];
511 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
512 sbt_field[0] = SP_NAME;
514 LOG_INFO("fixed missing field 'name'\n");
518 strunzone(sbt_field_title[sbt_num_fields]);
519 for(i = sbt_num_fields; i > 1; --i)
521 sbt_field_title[i] = sbt_field_title[i-1];
522 sbt_field_size[i] = sbt_field_size[i-1];
523 sbt_field[i] = sbt_field[i-1];
525 sbt_field_title[1] = strzone("|");
526 sbt_field[1] = SP_SEPARATOR;
527 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
529 LOG_INFO("fixed missing field '|'\n");
532 else if(!have_separator)
534 strunzone(sbt_field_title[sbt_num_fields]);
535 sbt_field_title[sbt_num_fields] = strzone("|");
536 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
537 sbt_field[sbt_num_fields] = SP_SEPARATOR;
539 LOG_INFO("fixed missing field '|'\n");
543 strunzone(sbt_field_title[sbt_num_fields]);
544 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
545 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
546 sbt_field[sbt_num_fields] = ps_secondary;
548 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
552 strunzone(sbt_field_title[sbt_num_fields]);
553 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
554 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
555 sbt_field[sbt_num_fields] = ps_primary;
557 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
561 sbt_field[sbt_num_fields] = SP_END;
565 vector sbt_field_rgb;
566 string sbt_field_icon0;
567 string sbt_field_icon1;
568 string sbt_field_icon2;
569 vector sbt_field_icon0_rgb;
570 vector sbt_field_icon1_rgb;
571 vector sbt_field_icon2_rgb;
572 string Scoreboard_GetField(entity pl, PlayerScoreField field)
574 float tmp, num, denom;
577 sbt_field_rgb = '1 1 1';
578 sbt_field_icon0 = "";
579 sbt_field_icon1 = "";
580 sbt_field_icon2 = "";
581 sbt_field_icon0_rgb = '1 1 1';
582 sbt_field_icon1_rgb = '1 1 1';
583 sbt_field_icon2_rgb = '1 1 1';
588 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
589 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
593 tmp = max(0, min(220, f-80)) / 220;
594 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
600 f = pl.ping_packetloss;
601 tmp = pl.ping_movementloss;
602 if(f == 0 && tmp == 0)
604 str = ftos(ceil(f * 100));
606 str = strcat(str, "~", ftos(ceil(tmp * 100)));
607 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
608 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
612 if(ready_waiting && pl.ready)
614 sbt_field_icon0 = "gfx/scoreboard/player_ready";
618 f = entcs_GetClientColors(pl.sv_entnum);
620 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
621 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
622 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
623 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
624 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
627 return entcs_GetName(pl.sv_entnum);
630 f = pl.(scores(SP_KILLS));
631 f -= pl.(scores(SP_SUICIDES));
635 num = pl.(scores(SP_KILLS));
636 denom = pl.(scores(SP_DEATHS));
639 sbt_field_rgb = '0 1 0';
640 str = sprintf("%d", num);
641 } else if(num <= 0) {
642 sbt_field_rgb = '1 0 0';
643 str = sprintf("%.1f", num/denom);
645 str = sprintf("%.1f", num/denom);
649 f = pl.(scores(SP_KILLS));
650 f -= pl.(scores(SP_DEATHS));
653 sbt_field_rgb = '0 1 0';
655 sbt_field_rgb = '1 1 1';
657 sbt_field_rgb = '1 0 0';
663 float elo = pl.(scores(SP_ELO));
665 case -1: return "...";
666 case -2: return _("N/A");
667 default: return ftos(elo);
671 case SP_DMG: case SP_DMGTAKEN:
672 return sprintf("%.1f k", pl.(scores(field)) / 1000);
675 tmp = pl.(scores(field));
676 f = scores_flags(field);
677 if(field == ps_primary)
678 sbt_field_rgb = '1 1 0';
679 else if(field == ps_secondary)
680 sbt_field_rgb = '0 1 1';
682 sbt_field_rgb = '1 1 1';
683 return ScoreString(f, tmp);
688 float sbt_fixcolumnwidth_len;
689 float sbt_fixcolumnwidth_iconlen;
690 float sbt_fixcolumnwidth_marginlen;
692 string Scoreboard_FixColumnWidth(int i, string str)
698 sbt_fixcolumnwidth_iconlen = 0;
700 if(sbt_field_icon0 != "")
702 sz = draw_getimagesize(sbt_field_icon0);
704 if(sbt_fixcolumnwidth_iconlen < f)
705 sbt_fixcolumnwidth_iconlen = f;
708 if(sbt_field_icon1 != "")
710 sz = draw_getimagesize(sbt_field_icon1);
712 if(sbt_fixcolumnwidth_iconlen < f)
713 sbt_fixcolumnwidth_iconlen = f;
716 if(sbt_field_icon2 != "")
718 sz = draw_getimagesize(sbt_field_icon2);
720 if(sbt_fixcolumnwidth_iconlen < f)
721 sbt_fixcolumnwidth_iconlen = f;
724 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
726 if(sbt_fixcolumnwidth_iconlen != 0)
727 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
729 sbt_fixcolumnwidth_marginlen = 0;
731 if(sbt_field[i] == SP_NAME) // name gets all remaining space
734 float remaining_space = 0;
735 for(j = 0; j < sbt_num_fields; ++j)
737 if (sbt_field[i] != SP_SEPARATOR)
738 remaining_space += sbt_field_size[j] + hud_fontsize.x;
739 sbt_field_size[i] = panel_size.x - remaining_space;
741 if (sbt_fixcolumnwidth_iconlen != 0)
742 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
743 float namesize = panel_size.x - remaining_space;
744 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
745 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
747 max_namesize = vid_conwidth - remaining_space;
750 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
752 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
753 if(sbt_field_size[i] < f)
754 sbt_field_size[i] = f;
759 void Scoreboard_initFieldSizes()
761 for(int i = 0; i < sbt_num_fields; ++i)
762 Scoreboard_FixColumnWidth(i, "");
765 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
768 vector column_dim = eY * panel_size.y;
770 column_dim.y -= 1.25 * hud_fontsize.y;
771 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
772 pos.x += hud_fontsize.x * 0.5;
773 for(i = 0; i < sbt_num_fields; ++i)
775 if(sbt_field[i] == SP_SEPARATOR)
777 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
780 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
781 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
782 pos.x += column_dim.x;
784 if(sbt_field[i] == SP_SEPARATOR)
786 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
787 for(i = sbt_num_fields - 1; i > 0; --i)
789 if(sbt_field[i] == SP_SEPARATOR)
792 pos.x -= sbt_field_size[i];
797 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
798 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
801 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
802 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
803 pos.x -= hud_fontsize.x;
808 pos.y += 1.25 * hud_fontsize.y;
812 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
814 TC(bool, is_self); TC(int, pl_number);
816 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
818 vector h_pos = item_pos;
819 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
820 // alternated rows highlighting
822 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
823 else if((sbt_highlight) && (!(pl_number % 2)))
824 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
826 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
828 vector pos = item_pos;
829 pos.x += hud_fontsize.x * 0.5;
830 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
831 vector tmp = '0 0 0';
833 PlayerScoreField field;
834 for(i = 0; i < sbt_num_fields; ++i)
836 field = sbt_field[i];
837 if(field == SP_SEPARATOR)
840 if(is_spec && field != SP_NAME && field != SP_PING) {
841 pos.x += sbt_field_size[i] + hud_fontsize.x;
844 str = Scoreboard_GetField(pl, field);
845 str = Scoreboard_FixColumnWidth(i, str);
847 pos.x += sbt_field_size[i] + hud_fontsize.x;
849 if(field == SP_NAME) {
850 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
851 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
853 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
854 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
857 tmp.x = sbt_field_size[i] + hud_fontsize.x;
858 if(sbt_field_icon0 != "")
859 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);
860 if(sbt_field_icon1 != "")
861 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);
862 if(sbt_field_icon2 != "")
863 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);
866 if(sbt_field[i] == SP_SEPARATOR)
868 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
869 for(i = sbt_num_fields-1; i > 0; --i)
871 field = sbt_field[i];
872 if(field == SP_SEPARATOR)
875 if(is_spec && field != SP_NAME && field != SP_PING) {
876 pos.x -= sbt_field_size[i] + hud_fontsize.x;
880 str = Scoreboard_GetField(pl, field);
881 str = Scoreboard_FixColumnWidth(i, str);
883 if(field == SP_NAME) {
884 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
885 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
887 tmp.x = sbt_fixcolumnwidth_len;
888 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
891 tmp.x = sbt_field_size[i];
892 if(sbt_field_icon0 != "")
893 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);
894 if(sbt_field_icon1 != "")
895 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);
896 if(sbt_field_icon2 != "")
897 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);
898 pos.x -= sbt_field_size[i] + hud_fontsize.x;
903 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
906 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
909 vector h_pos = item_pos;
910 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
912 bool complete = (this_team == NUM_SPECTATOR);
915 if((sbt_highlight) && (!(pl_number % 2)))
916 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
918 vector pos = item_pos;
919 pos.x += hud_fontsize.x * 0.5;
920 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
922 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
924 width_limit -= stringwidth("...", false, hud_fontsize);
925 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
926 float ping_padding = 0;
927 float min_pingsize = stringwidth("999", false, hud_fontsize);
928 for(i = 0; pl; pl = pl.sort_next)
930 if(pl.team != this_team)
936 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
937 if(this_team == NUM_SPECTATOR)
939 if(autocvar_hud_panel_scoreboard_spectators_showping)
941 string ping = Scoreboard_GetField(pl, SP_PING);
942 float pingsize = stringwidth(ping, false, hud_fontsize);
943 if(min_pingsize > pingsize)
944 ping_padding = min_pingsize - pingsize;
945 string col = rgb_to_hexcolor(sbt_field_rgb);
946 str = sprintf("%s ^7[%s%s^7]", str, col, ping);
949 else if(autocvar_hud_panel_scoreboard_others_showscore)
950 str = sprintf("%s ^7(^3%s^7)", str, ftos(pl.(scores(ps_primary))));
951 float str_width = stringwidth(str, true, hud_fontsize);
952 if(pos.x + str_width > width_limit)
957 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
962 pos.x = item_pos.x + hud_fontsize.x * 0.5;
963 pos.y = item_pos.y + i * (hud_fontsize.y * 1.25);
966 drawcolorcodedstring(pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
967 pos.x += str_width + hud_fontsize.x * 0.5;
968 pos.x += ping_padding;
970 return eX * item_pos.x + eY * (item_pos.y + i * hud_fontsize.y * 1.25);
973 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
975 int max_players = 999;
976 if(autocvar_hud_panel_scoreboard_maxrows)
979 max_players = autocvar_hud_panel_scoreboard_maxrows_teamplayers;
981 max_players = autocvar_hud_panel_scoreboard_maxrows_players;
984 if(max_players == tm.team_size)
989 entity me = playerslots[current_player];
991 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
992 panel_size.y += panel_bg_padding * 2;
995 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
996 if(panel.current_panel_bg != "0")
997 end_pos.y += panel_bg_border * 2;
1001 panel_pos += '1 1 0' * panel_bg_padding;
1002 panel_size -= '2 2 0' * panel_bg_padding;
1006 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
1010 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1012 pos.y += 1.25 * hud_fontsize.y;
1015 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1017 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1020 // print header row and highlight columns
1021 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1023 // fill the table and draw the rows
1024 bool is_self = false;
1025 bool self_shown = false;
1027 for(pl = players.sort_next; pl; pl = pl.sort_next)
1029 if(pl.team != tm.team)
1031 if(i == max_players - 2 && pl != me)
1033 if(!self_shown && me.team == tm.team)
1035 Scoreboard_DrawItem(pos, rgb, me, true, i);
1037 pos.y += 1.25 * hud_fontsize.y;
1041 if(i >= max_players - 1)
1043 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1046 is_self = (pl.sv_entnum == current_player);
1047 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1050 pos.y += 1.25 * hud_fontsize.y;
1054 panel_size.x += panel_bg_padding * 2; // restore initial width
1058 float Scoreboard_WouldDraw() {
1059 if (QuickMenu_IsOpened())
1061 else if (HUD_Radar_Clickable())
1063 else if (scoreboard_showscores)
1065 else if (intermission == 1)
1067 else if (intermission == 2)
1069 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1071 else if (scoreboard_showscores_force)
1076 float average_accuracy;
1077 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1079 WepSet weapons_stat = WepSet_GetFromStat();
1080 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1081 int disownedcnt = 0;
1083 FOREACH(Weapons, it != WEP_Null, {
1084 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1086 WepSet set = it.m_wepset;
1087 if (weapon_stats < 0)
1089 if (!(weapons_stat & set) && (it.spawnflags & WEP_FLAG_HIDDEN || it.spawnflags & WEP_FLAG_MUTATORBLOCKED))
1091 else if (!(weapons_stat & set || weapons_inmap & set))
1096 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1097 if (weapon_cnt <= 0) return pos;
1100 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1102 int columnns = ceil(weapon_cnt / rows);
1104 float weapon_height = 29;
1105 float height = hud_fontsize.y + weapon_height;
1107 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1108 pos.y += 1.25 * hud_fontsize.y;
1109 if(panel.current_panel_bg != "0")
1110 pos.y += panel_bg_border;
1113 panel_size.y = height * rows;
1114 panel_size.y += panel_bg_padding * 2;
1117 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1118 if(panel.current_panel_bg != "0")
1119 end_pos.y += panel_bg_border * 2;
1121 if(panel_bg_padding)
1123 panel_pos += '1 1 0' * panel_bg_padding;
1124 panel_size -= '2 2 0' * panel_bg_padding;
1128 vector tmp = panel_size;
1130 float weapon_width = tmp.x / columnns / rows;
1133 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1137 // column highlighting
1138 for (int i = 0; i < columnns; ++i)
1140 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1143 for (int i = 0; i < rows; ++i)
1144 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * hud_fontsize.y, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1147 average_accuracy = 0;
1148 int weapons_with_stats = 0;
1150 pos.x += weapon_width / 2;
1152 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1155 Accuracy_LoadColors();
1157 float oldposx = pos.x;
1161 FOREACH(Weapons, it != WEP_Null, {
1162 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1164 WepSet set = it.m_wepset;
1165 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1169 if (weapon_stats >= 0)
1170 weapon_alpha = sbt_fg_alpha;
1172 weapon_alpha = 0.2 * sbt_fg_alpha;
1175 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1177 if (weapon_stats >= 0) {
1178 weapons_with_stats += 1;
1179 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1182 s = sprintf("%d%%", weapon_stats * 100);
1185 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1187 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1188 rgb = Accuracy_GetColor(weapon_stats);
1190 drawstring(tmpos + eX * padding + eY * weapon_height, s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1192 tmpos.x += weapon_width * rows;
1193 pos.x += weapon_width * rows;
1194 if (rows == 2 && column == columnns - 1) {
1202 if (weapons_with_stats)
1203 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1205 panel_size.x += panel_bg_padding * 2; // restore initial width
1209 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1211 pos.x += hud_fontsize.x * 0.25;
1212 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1213 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1214 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1216 pos.y += hud_fontsize.y;
1221 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1222 float stat_secrets_found, stat_secrets_total;
1223 float stat_monsters_killed, stat_monsters_total;
1227 // get monster stats
1228 stat_monsters_killed = STAT(MONSTERS_KILLED);
1229 stat_monsters_total = STAT(MONSTERS_TOTAL);
1231 // get secrets stats
1232 stat_secrets_found = STAT(SECRETS_FOUND);
1233 stat_secrets_total = STAT(SECRETS_TOTAL);
1235 // get number of rows
1236 if(stat_secrets_total)
1238 if(stat_monsters_total)
1241 // if no rows, return
1245 // draw table header
1246 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1247 pos.y += 1.25 * hud_fontsize.y;
1248 if(panel.current_panel_bg != "0")
1249 pos.y += panel_bg_border;
1252 panel_size.y = hud_fontsize.y * rows;
1253 panel_size.y += panel_bg_padding * 2;
1256 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1257 if(panel.current_panel_bg != "0")
1258 end_pos.y += panel_bg_border * 2;
1260 if(panel_bg_padding)
1262 panel_pos += '1 1 0' * panel_bg_padding;
1263 panel_size -= '2 2 0' * panel_bg_padding;
1267 vector tmp = panel_size;
1270 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1273 if(stat_monsters_total)
1275 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1276 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1280 if(stat_secrets_total)
1282 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1283 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1286 panel_size.x += panel_bg_padding * 2; // restore initial width
1291 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1294 RANKINGS_RECEIVED_CNT = 0;
1295 for (i=RANKINGS_CNT-1; i>=0; --i)
1297 ++RANKINGS_RECEIVED_CNT;
1299 if (RANKINGS_RECEIVED_CNT == 0)
1302 vector hl_rgb = rgb + '0.5 0.5 0.5';
1304 pos.y += hud_fontsize.y;
1305 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1306 pos.y += 1.25 * hud_fontsize.y;
1307 if(panel.current_panel_bg != "0")
1308 pos.y += panel_bg_border;
1311 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1312 panel_size.y += panel_bg_padding * 2;
1315 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1316 if(panel.current_panel_bg != "0")
1317 end_pos.y += panel_bg_border * 2;
1319 if(panel_bg_padding)
1321 panel_pos += '1 1 0' * panel_bg_padding;
1322 panel_size -= '2 2 0' * panel_bg_padding;
1326 vector tmp = panel_size;
1329 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1332 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1339 n = grecordholder[i];
1340 p = count_ordinal(i+1);
1341 if(grecordholder[i] == entcs_GetName(player_localnum))
1342 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1343 else if(!(i % 2) && sbt_highlight)
1344 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1345 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1346 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);
1347 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1348 pos.y += 1.25 * hud_fontsize.y;
1351 panel_size.x += panel_bg_padding * 2; // restore initial width
1355 void Scoreboard_Draw()
1357 if(!autocvar__hud_configure)
1359 if(!hud_draw_maximized) return;
1361 // frametime checks allow to toggle the scoreboard even when the game is paused
1362 if(scoreboard_active) {
1363 if(hud_configure_menu_open == 1)
1364 scoreboard_fade_alpha = 1;
1365 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1366 if (scoreboard_fadeinspeed && frametime)
1367 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1369 scoreboard_fade_alpha = 1;
1370 if(hud_fontsize_str != autocvar_hud_fontsize)
1372 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1373 Scoreboard_initFieldSizes();
1374 if(hud_fontsize_str)
1375 strunzone(hud_fontsize_str);
1376 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1380 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1381 if (scoreboard_fadeoutspeed && frametime)
1382 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1384 scoreboard_fade_alpha = 0;
1387 if (!scoreboard_fade_alpha)
1391 scoreboard_fade_alpha = 0;
1393 if (autocvar_hud_panel_scoreboard_dynamichud)
1396 HUD_Scale_Disable();
1398 if(scoreboard_fade_alpha <= 0)
1400 panel_fade_alpha *= scoreboard_fade_alpha;
1401 HUD_Panel_LoadCvars();
1403 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1404 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1405 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1406 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1407 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1408 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1410 // don't overlap with con_notify
1411 if(!autocvar__hud_configure)
1412 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1414 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1415 float fixed_scoreboard_width = bound(vid_conwidth * 0.4, vid_conwidth - excess, vid_conwidth * 0.93);
1416 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1417 panel_size.x = fixed_scoreboard_width;
1419 Scoreboard_UpdatePlayerTeams();
1421 vector pos = panel_pos;
1426 vector sb_heading_fontsize;
1427 sb_heading_fontsize = hud_fontsize * 2;
1428 draw_beginBoldFont();
1429 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1432 pos.y += sb_heading_fontsize.y;
1433 if(panel.current_panel_bg != "0")
1434 pos.y += panel_bg_border;
1436 // Draw the scoreboard
1437 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1440 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1444 vector panel_bg_color_save = panel_bg_color;
1445 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1446 if(panel.current_panel_bg != "0")
1447 team_score_baseoffset.x -= panel_bg_border;
1448 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1450 if(tm.team == NUM_SPECTATOR)
1455 draw_beginBoldFont();
1456 vector rgb = Team_ColorRGB(tm.team);
1457 str = ftos(tm.(teamscores(ts_primary)));
1458 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1460 if(ts_primary != ts_secondary)
1462 str = ftos(tm.(teamscores(ts_secondary)));
1463 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);
1466 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1467 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1468 else if(panel_bg_color_team > 0)
1469 panel_bg_color = rgb * panel_bg_color_team;
1471 panel_bg_color = rgb;
1472 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1474 panel_bg_color = panel_bg_color_save;
1478 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1479 if(tm.team != NUM_SPECTATOR)
1481 // display it anyway
1482 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);
1502 for(pl = players.sort_next; pl; pl = pl.sort_next)
1504 if(pl.team == NUM_SPECTATOR)
1506 draw_beginBoldFont();
1507 drawstring(pos, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1509 pos.y += 1.25 * hud_fontsize.y;
1511 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1512 pos.y += 1.25 * hud_fontsize.y;
1518 // Print info string
1520 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1521 tl = STAT(TIMELIMIT);
1522 fl = STAT(FRAGLIMIT);
1523 ll = STAT(LEADLIMIT);
1524 if(gametype == MAPINFO_TYPE_LMS)
1527 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1532 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1536 str = strcat(str, _(" or"));
1539 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1540 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1541 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1542 TranslateScoresLabel(teamscores_label(ts_primary))));
1546 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1547 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1548 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1549 TranslateScoresLabel(scores_label(ps_primary))));
1554 if(tl > 0 || fl > 0)
1555 str = strcat(str, _(" or"));
1558 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1559 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1560 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1561 TranslateScoresLabel(teamscores_label(ts_primary))));
1565 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1566 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1567 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1568 TranslateScoresLabel(scores_label(ps_primary))));
1573 pos.y += 1.2 * hud_fontsize.y;
1574 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1576 // print information about respawn status
1577 float respawn_time = STAT(RESPAWN_TIME);
1581 if(respawn_time < 0)
1583 // a negative number means we are awaiting respawn, time value is still the same
1584 respawn_time *= -1; // remove mark now that we checked it
1586 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1587 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1589 str = sprintf(_("^1Respawning in ^3%s^1..."),
1590 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1591 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1593 count_seconds(ceil(respawn_time - time))
1597 else if(time < respawn_time)
1599 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1600 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1601 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1603 count_seconds(ceil(respawn_time - time))
1607 else if(time >= respawn_time)
1608 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1610 pos.y += 1.2 * hud_fontsize.y;
1611 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1614 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;