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;
58 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
61 void drawstringright(vector, string, vector, vector, float, float);
62 void drawstringcenter(vector, string, vector, vector, float, float);
64 // wrapper to put all possible scores titles through gettext
65 string TranslateScoresLabel(string l)
69 case "bckills": return CTX(_("SCO^bckills"));
70 case "bctime": return CTX(_("SCO^bctime"));
71 case "caps": return CTX(_("SCO^caps"));
72 case "captime": return CTX(_("SCO^captime"));
73 case "deaths": return CTX(_("SCO^deaths"));
74 case "destroyed": return CTX(_("SCO^destroyed"));
75 case "dmg": return CTX(_("SCO^damage"));
76 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
77 case "drops": return CTX(_("SCO^drops"));
78 case "faults": return CTX(_("SCO^faults"));
79 case "fckills": return CTX(_("SCO^fckills"));
80 case "goals": return CTX(_("SCO^goals"));
81 case "kckills": return CTX(_("SCO^kckills"));
82 case "kdratio": return CTX(_("SCO^kdratio"));
83 case "kd": return CTX(_("SCO^k/d"));
84 case "kdr": return CTX(_("SCO^kdr"));
85 case "kills": return CTX(_("SCO^kills"));
86 case "laps": return CTX(_("SCO^laps"));
87 case "lives": return CTX(_("SCO^lives"));
88 case "losses": return CTX(_("SCO^losses"));
89 case "name": return CTX(_("SCO^name"));
90 case "sum": return CTX(_("SCO^sum"));
91 case "nick": return CTX(_("SCO^nick"));
92 case "objectives": return CTX(_("SCO^objectives"));
93 case "pickups": return CTX(_("SCO^pickups"));
94 case "ping": return CTX(_("SCO^ping"));
95 case "pl": return CTX(_("SCO^pl"));
96 case "pushes": return CTX(_("SCO^pushes"));
97 case "rank": return CTX(_("SCO^rank"));
98 case "returns": return CTX(_("SCO^returns"));
99 case "revivals": return CTX(_("SCO^revivals"));
100 case "rounds": return CTX(_("SCO^rounds won"));
101 case "score": return CTX(_("SCO^score"));
102 case "suicides": return CTX(_("SCO^suicides"));
103 case "takes": return CTX(_("SCO^takes"));
104 case "ticks": return CTX(_("SCO^ticks"));
109 void Scoreboard_InitScores()
113 ps_primary = ps_secondary = NULL;
114 ts_primary = ts_secondary = -1;
115 FOREACH(Scores, true, {
116 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
117 if(f == SFL_SORT_PRIO_PRIMARY)
119 if(f == SFL_SORT_PRIO_SECONDARY)
122 if(ps_secondary == NULL)
123 ps_secondary = ps_primary;
125 for(i = 0; i < MAX_TEAMSCORE; ++i)
127 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
128 if(f == SFL_SORT_PRIO_PRIMARY)
130 if(f == SFL_SORT_PRIO_SECONDARY)
133 if(ts_secondary == -1)
134 ts_secondary = ts_primary;
136 Cmd_Scoreboard_SetFields(0);
139 float SetTeam(entity pl, float Team);
141 void Scoreboard_UpdatePlayerTeams()
148 for(pl = players.sort_next; pl; pl = pl.sort_next)
151 Team = entcs_GetScoreTeam(pl.sv_entnum);
152 if(SetTeam(pl, Team))
155 Scoreboard_UpdatePlayerPos(pl);
159 pl = players.sort_next;
164 print(strcat("PNUM: ", ftos(num), "\n"));
169 int Scoreboard_CompareScore(int vl, int vr, int f)
171 TC(int, vl); TC(int, vr); TC(int, f);
172 if(f & SFL_ZERO_IS_WORST)
174 if(vl == 0 && vr != 0)
176 if(vl != 0 && vr == 0)
180 return IS_INCREASING(f);
182 return IS_DECREASING(f);
186 float Scoreboard_ComparePlayerScores(entity left, entity right)
189 vl = entcs_GetTeam(left.sv_entnum);
190 vr = entcs_GetTeam(right.sv_entnum);
202 if(vl == NUM_SPECTATOR)
204 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
206 if(!left.gotscores && right.gotscores)
211 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
215 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
219 FOREACH(Scores, true, {
220 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
221 if (r >= 0) return r;
224 if (left.sv_entnum < right.sv_entnum)
230 void Scoreboard_UpdatePlayerPos(entity player)
233 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
235 SORT_SWAP(player, ent);
237 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
239 SORT_SWAP(ent, player);
243 float Scoreboard_CompareTeamScores(entity left, entity right)
247 if(left.team == NUM_SPECTATOR)
249 if(right.team == NUM_SPECTATOR)
252 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
256 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
260 for(i = 0; i < MAX_TEAMSCORE; ++i)
262 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
267 if (left.team < right.team)
273 void Scoreboard_UpdateTeamPos(entity Team)
276 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
278 SORT_SWAP(Team, ent);
280 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
282 SORT_SWAP(ent, Team);
286 void Cmd_Scoreboard_Help()
288 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
289 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
290 LOG_INFO(_("Usage:\n"));
291 LOG_INFO(_("^2scoreboard_columns_set default\n"));
292 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
293 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
294 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
297 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
298 LOG_INFO(_("^3ping^7 Ping time\n"));
299 LOG_INFO(_("^3pl^7 Packet loss\n"));
300 LOG_INFO(_("^3elo^7 Player ELO\n"));
301 LOG_INFO(_("^3kills^7 Number of kills\n"));
302 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
303 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
304 LOG_INFO(_("^3frags^7 kills - suicides\n"));
305 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
306 LOG_INFO(_("^3dmg^7 The total damage done\n"));
307 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
308 LOG_INFO(_("^3sum^7 frags - deaths\n"));
309 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
310 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
311 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
312 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
313 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
314 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
315 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
316 LOG_INFO(_("^3rank^7 Player rank\n"));
317 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
318 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
319 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
320 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
321 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
322 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
323 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
324 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
325 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
326 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
327 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
328 LOG_INFO(_("^3score^7 Total score\n"));
331 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
332 "of game types, then a slash, to make the field show up only in these\n"
333 "or in all but these game types. You can also specify 'all' as a\n"
334 "field to show all fields available for the current game mode.\n\n"));
336 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
337 "include/exclude ALL teams/noteams game modes.\n\n"));
339 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
340 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
341 "right of the vertical bar aligned to the right.\n"));
342 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
343 "other gamemodes except DM.\n"));
346 // NOTE: adding a gametype with ? to not warn for an optional field
347 // make sure it's excluded in a previous exclusive rule, if any
348 // otherwise the previous exclusive rule warns anyway
349 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
350 #define SCOREBOARD_DEFAULT_COLUMNS \
352 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
353 " -teams,lms/deaths +ft,tdm/deaths" \
354 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
355 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
356 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
357 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
358 " +lms/lives +lms/rank" \
359 " +kh/caps +kh/pushes +kh/destroyed" \
360 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
361 " +as/objectives +nb/faults +nb/goals" \
362 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
363 " -lms,rc,cts,inv,nb/score"
365 void Cmd_Scoreboard_SetFields(int argc)
370 float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
374 return; // do nothing, we don't know gametype and scores yet
376 // sbt_fields uses strunzone on the titles!
377 if(!sbt_field_title[0])
378 for(i = 0; i < MAX_SBT_FIELDS; ++i)
379 sbt_field_title[i] = strzone("(null)");
381 // TODO: re enable with gametype dependant cvars?
382 if(argc < 3) // no arguments provided
383 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
386 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
390 if(argv(2) == "default")
391 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
392 else if(argv(2) == "all")
395 s = "ping pl name |";
396 FOREACH(Scores, true, {
398 if(it != ps_secondary)
399 if(scores_label(it) != "")
400 s = strcat(s, " ", scores_label(it));
402 if(ps_secondary != ps_primary)
403 s = strcat(s, " ", scores_label(ps_secondary));
404 s = strcat(s, " ", scores_label(ps_primary));
405 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
412 hud_fontsize = HUD_GetFontsize("hud_fontsize");
414 for(i = 1; i < argc - 1; ++i)
420 if(substring(str, 0, 1) == "?")
423 str = substring(str, 1, strlen(str) - 1);
426 slash = strstrofs(str, "/", 0);
429 pattern = substring(str, 0, slash);
430 str = substring(str, slash + 1, strlen(str) - (slash + 1));
432 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
436 strunzone(sbt_field_title[sbt_num_fields]);
437 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
438 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
439 str = strtolower(str);
444 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
445 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
446 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
447 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
448 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
449 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
450 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
451 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
452 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
455 FOREACH(Scores, true, {
456 if (str == strtolower(scores_label(it))) {
458 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
468 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
472 sbt_field[sbt_num_fields] = j;
475 if(j == ps_secondary)
481 if(sbt_num_fields >= MAX_SBT_FIELDS)
485 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
487 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
489 if(ps_primary == ps_secondary)
491 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
493 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
497 strunzone(sbt_field_title[sbt_num_fields]);
498 for(i = sbt_num_fields; i > 0; --i)
500 sbt_field_title[i] = sbt_field_title[i-1];
501 sbt_field_size[i] = sbt_field_size[i-1];
502 sbt_field[i] = sbt_field[i-1];
504 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
505 sbt_field[0] = SP_NAME;
507 LOG_INFO("fixed missing field 'name'\n");
511 strunzone(sbt_field_title[sbt_num_fields]);
512 for(i = sbt_num_fields; i > 1; --i)
514 sbt_field_title[i] = sbt_field_title[i-1];
515 sbt_field_size[i] = sbt_field_size[i-1];
516 sbt_field[i] = sbt_field[i-1];
518 sbt_field_title[1] = strzone("|");
519 sbt_field[1] = SP_SEPARATOR;
520 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
522 LOG_INFO("fixed missing field '|'\n");
525 else if(!have_separator)
527 strunzone(sbt_field_title[sbt_num_fields]);
528 sbt_field_title[sbt_num_fields] = strzone("|");
529 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
530 sbt_field[sbt_num_fields] = SP_SEPARATOR;
532 LOG_INFO("fixed missing field '|'\n");
536 strunzone(sbt_field_title[sbt_num_fields]);
537 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
538 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
539 sbt_field[sbt_num_fields] = ps_secondary;
541 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
545 strunzone(sbt_field_title[sbt_num_fields]);
546 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
547 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
548 sbt_field[sbt_num_fields] = ps_primary;
550 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
554 sbt_field[sbt_num_fields] = SP_END;
558 vector sbt_field_rgb;
559 string sbt_field_icon0;
560 string sbt_field_icon1;
561 string sbt_field_icon2;
562 vector sbt_field_icon0_rgb;
563 vector sbt_field_icon1_rgb;
564 vector sbt_field_icon2_rgb;
565 float sbt_field_icon0_alpha;
566 float sbt_field_icon1_alpha;
567 float sbt_field_icon2_alpha;
568 string Scoreboard_GetField(entity pl, PlayerScoreField field)
570 float tmp, num, denom;
573 sbt_field_rgb = '1 1 1';
574 sbt_field_icon0 = "";
575 sbt_field_icon1 = "";
576 sbt_field_icon2 = "";
577 sbt_field_icon0_rgb = '1 1 1';
578 sbt_field_icon1_rgb = '1 1 1';
579 sbt_field_icon2_rgb = '1 1 1';
580 sbt_field_icon0_alpha = 1;
581 sbt_field_icon1_alpha = 1;
582 sbt_field_icon2_alpha = 1;
587 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
588 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
592 tmp = max(0, min(220, f-80)) / 220;
593 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
599 f = pl.ping_packetloss;
600 tmp = pl.ping_movementloss;
601 if(f == 0 && tmp == 0)
603 str = ftos(ceil(f * 100));
605 str = strcat(str, "~", ftos(ceil(tmp * 100)));
606 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
607 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
611 if(ready_waiting && pl.ready)
613 sbt_field_icon0 = "gfx/scoreboard/player_ready";
617 f = entcs_GetClientColors(pl.sv_entnum);
619 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
620 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
621 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
622 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
623 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
626 return entcs_GetName(pl.sv_entnum);
629 f = pl.(scores(SP_KILLS));
630 f -= pl.(scores(SP_SUICIDES));
634 num = pl.(scores(SP_KILLS));
635 denom = pl.(scores(SP_DEATHS));
638 sbt_field_rgb = '0 1 0';
639 str = sprintf("%d", num);
640 } else if(num <= 0) {
641 sbt_field_rgb = '1 0 0';
642 str = sprintf("%.1f", num/denom);
644 str = sprintf("%.1f", num/denom);
648 f = pl.(scores(SP_KILLS));
649 f -= pl.(scores(SP_DEATHS));
652 sbt_field_rgb = '0 1 0';
654 sbt_field_rgb = '1 1 1';
656 sbt_field_rgb = '1 0 0';
662 float elo = pl.(scores(SP_ELO));
664 case -1: return "...";
665 case -2: return _("N/A");
666 default: return ftos(elo);
670 case SP_DMG: case SP_DMGTAKEN:
671 return sprintf("%.1f k", pl.(scores(field)) / 1000);
674 tmp = pl.(scores(field));
675 f = scores_flags(field);
676 if(field == ps_primary)
677 sbt_field_rgb = '1 1 0';
678 else if(field == ps_secondary)
679 sbt_field_rgb = '0 1 1';
681 sbt_field_rgb = '1 1 1';
682 return ScoreString(f, tmp);
687 float sbt_fixcolumnwidth_len;
688 float sbt_fixcolumnwidth_iconlen;
689 float sbt_fixcolumnwidth_marginlen;
691 string Scoreboard_FixColumnWidth(int i, string str)
697 sbt_fixcolumnwidth_iconlen = 0;
699 if(sbt_field_icon0 != "")
701 sz = draw_getimagesize(sbt_field_icon0);
703 if(sbt_fixcolumnwidth_iconlen < f)
704 sbt_fixcolumnwidth_iconlen = f;
707 if(sbt_field_icon1 != "")
709 sz = draw_getimagesize(sbt_field_icon1);
711 if(sbt_fixcolumnwidth_iconlen < f)
712 sbt_fixcolumnwidth_iconlen = f;
715 if(sbt_field_icon2 != "")
717 sz = draw_getimagesize(sbt_field_icon2);
719 if(sbt_fixcolumnwidth_iconlen < f)
720 sbt_fixcolumnwidth_iconlen = f;
723 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
725 if(sbt_fixcolumnwidth_iconlen != 0)
726 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
728 sbt_fixcolumnwidth_marginlen = 0;
730 if(sbt_field[i] == SP_NAME) // name gets all remaining space
733 float remaining_space = 0;
734 for(j = 0; j < sbt_num_fields; ++j)
736 if (sbt_field[i] != SP_SEPARATOR)
737 remaining_space += sbt_field_size[j] + hud_fontsize.x;
738 sbt_field_size[i] = panel_size.x - remaining_space;
740 if (sbt_fixcolumnwidth_iconlen != 0)
741 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
742 float namesize = panel_size.x - remaining_space;
743 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
744 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
746 max_namesize = vid_conwidth - remaining_space;
749 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
751 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
752 if(sbt_field_size[i] < f)
753 sbt_field_size[i] = f;
758 void Scoreboard_initFieldSizes()
760 for(int i = 0; i < sbt_num_fields; ++i)
761 Scoreboard_FixColumnWidth(i, "");
764 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
767 vector column_dim = eY * panel_size.y;
769 column_dim.y -= 1.25 * hud_fontsize.y;
770 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
771 pos.x += hud_fontsize.x * 0.5;
772 for(i = 0; i < sbt_num_fields; ++i)
774 if(sbt_field[i] == SP_SEPARATOR)
776 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
779 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
780 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
781 pos.x += column_dim.x;
783 if(sbt_field[i] == SP_SEPARATOR)
785 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
786 for(i = sbt_num_fields - 1; i > 0; --i)
788 if(sbt_field[i] == SP_SEPARATOR)
791 pos.x -= sbt_field_size[i];
796 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
797 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
800 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
801 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
802 pos.x -= hud_fontsize.x;
807 pos.y += 1.25 * hud_fontsize.y;
811 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
813 TC(bool, is_self); TC(int, pl_number);
815 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
817 vector h_pos = item_pos;
818 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
819 // alternated rows highlighting
821 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
822 else if((sbt_highlight) && (!(pl_number % 2)))
823 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
825 vector pos = item_pos;
826 pos.x += hud_fontsize.x * 0.5;
827 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
828 vector tmp = '0 0 0';
830 PlayerScoreField field;
831 for(i = 0; i < sbt_num_fields; ++i)
833 field = sbt_field[i];
834 if(field == SP_SEPARATOR)
837 if(is_spec && field != SP_NAME && field != SP_PING) {
838 pos.x += sbt_field_size[i] + hud_fontsize.x;
841 str = Scoreboard_GetField(pl, field);
842 str = Scoreboard_FixColumnWidth(i, str);
844 pos.x += sbt_field_size[i] + hud_fontsize.x;
846 if(field == SP_NAME) {
847 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
849 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
851 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
853 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
855 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
857 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
860 tmp.x = sbt_field_size[i] + hud_fontsize.x;
861 if(sbt_field_icon0 != "")
863 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);
865 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);
866 if(sbt_field_icon1 != "")
868 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);
870 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);
871 if(sbt_field_icon2 != "")
873 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);
875 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);
878 if(sbt_field[i] == SP_SEPARATOR)
880 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
881 for(i = sbt_num_fields-1; i > 0; --i)
883 field = sbt_field[i];
884 if(field == SP_SEPARATOR)
887 if(is_spec && field != SP_NAME && field != SP_PING) {
888 pos.x -= sbt_field_size[i] + hud_fontsize.x;
892 str = Scoreboard_GetField(pl, field);
893 str = Scoreboard_FixColumnWidth(i, str);
895 if(field == SP_NAME) {
896 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
898 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
900 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
902 tmp.x = sbt_fixcolumnwidth_len;
904 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
906 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
909 tmp.x = sbt_field_size[i];
910 if(sbt_field_icon0 != "")
912 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);
914 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);
915 if(sbt_field_icon1 != "")
917 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);
919 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);
920 if(sbt_field_icon2 != "")
922 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);
924 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);
925 pos.x -= sbt_field_size[i] + hud_fontsize.x;
930 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
933 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
936 vector h_pos = item_pos;
937 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
939 bool complete = (this_team == NUM_SPECTATOR);
942 if((sbt_highlight) && (!(pl_number % 2)))
943 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
945 vector pos = item_pos;
946 pos.x += hud_fontsize.x * 0.5;
947 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
949 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
951 width_limit -= stringwidth("...", false, hud_fontsize);
952 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
953 float ping_padding = 0;
954 float min_pingsize = stringwidth("999", false, hud_fontsize);
955 for(i = 0; pl; pl = pl.sort_next)
957 if(pl.team != this_team)
963 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
964 if(this_team == NUM_SPECTATOR)
966 if(autocvar_hud_panel_scoreboard_spectators_showping)
968 string ping = Scoreboard_GetField(pl, SP_PING);
969 float pingsize = stringwidth(ping, false, hud_fontsize);
970 if(min_pingsize > pingsize)
971 ping_padding = min_pingsize - pingsize;
972 string col = rgb_to_hexcolor(sbt_field_rgb);
973 str = sprintf("%s ^7[%s%s^7]", str, col, ping);
976 else if(autocvar_hud_panel_scoreboard_others_showscore)
977 str = sprintf("%s ^7(^3%s^7)", str, ftos(pl.(scores(ps_primary))));
978 float str_width = stringwidth(str, true, hud_fontsize);
979 if(pos.x + str_width > width_limit)
984 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
989 pos.x = item_pos.x + hud_fontsize.x * 0.5;
990 pos.y = item_pos.y + i * (hud_fontsize.y * 1.25);
993 drawcolorcodedstring(pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
994 pos.x += str_width + hud_fontsize.x * 0.5;
995 pos.x += ping_padding;
997 return eX * item_pos.x + eY * (item_pos.y + i * hud_fontsize.y * 1.25);
1000 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1002 int max_players = 999;
1003 if(autocvar_hud_panel_scoreboard_maxrows)
1006 max_players = autocvar_hud_panel_scoreboard_maxrows_teamplayers;
1008 max_players = autocvar_hud_panel_scoreboard_maxrows_players;
1009 if(max_players <= 1)
1011 if(max_players == tm.team_size)
1016 entity me = playerslots[current_player];
1018 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1019 panel_size.y += panel_bg_padding * 2;
1022 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1023 if(panel.current_panel_bg != "0")
1024 end_pos.y += panel_bg_border * 2;
1026 if(panel_bg_padding)
1028 panel_pos += '1 1 0' * panel_bg_padding;
1029 panel_size -= '2 2 0' * panel_bg_padding;
1033 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
1037 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1039 pos.y += 1.25 * hud_fontsize.y;
1042 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1044 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1047 // print header row and highlight columns
1048 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1050 // fill the table and draw the rows
1051 bool is_self = false;
1052 bool self_shown = false;
1054 for(pl = players.sort_next; pl; pl = pl.sort_next)
1056 if(pl.team != tm.team)
1058 if(i == max_players - 2 && pl != me)
1060 if(!self_shown && me.team == tm.team)
1062 Scoreboard_DrawItem(pos, rgb, me, true, i);
1064 pos.y += 1.25 * hud_fontsize.y;
1068 if(i >= max_players - 1)
1070 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1073 is_self = (pl.sv_entnum == current_player);
1074 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1077 pos.y += 1.25 * hud_fontsize.y;
1081 panel_size.x += panel_bg_padding * 2; // restore initial width
1085 float Scoreboard_WouldDraw() {
1086 if (QuickMenu_IsOpened())
1088 else if (HUD_Radar_Clickable())
1090 else if (scoreboard_showscores)
1092 else if (intermission == 1)
1094 else if (intermission == 2)
1096 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1098 else if (scoreboard_showscores_force)
1103 float average_accuracy;
1104 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1106 WepSet weapons_stat = WepSet_GetFromStat();
1107 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1108 int disownedcnt = 0;
1110 FOREACH(Weapons, it != WEP_Null, {
1111 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1113 WepSet set = it.m_wepset;
1114 if (weapon_stats < 0)
1116 if (!(weapons_stat & set) && (it.spawnflags & WEP_FLAG_HIDDEN || it.spawnflags & WEP_FLAG_MUTATORBLOCKED))
1118 else if (!(weapons_stat & set || weapons_inmap & set))
1123 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1124 if (weapon_cnt <= 0) return pos;
1127 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1129 int columnns = ceil(weapon_cnt / rows);
1131 float weapon_height = 29;
1132 float height = hud_fontsize.y + weapon_height;
1134 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1135 pos.y += 1.25 * hud_fontsize.y;
1136 if(panel.current_panel_bg != "0")
1137 pos.y += panel_bg_border;
1140 panel_size.y = height * rows;
1141 panel_size.y += panel_bg_padding * 2;
1144 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1145 if(panel.current_panel_bg != "0")
1146 end_pos.y += panel_bg_border * 2;
1148 if(panel_bg_padding)
1150 panel_pos += '1 1 0' * panel_bg_padding;
1151 panel_size -= '2 2 0' * panel_bg_padding;
1155 vector tmp = panel_size;
1157 float weapon_width = tmp.x / columnns / rows;
1160 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1164 // column highlighting
1165 for (int i = 0; i < columnns; ++i)
1167 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1170 for (int i = 0; i < rows; ++i)
1171 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * hud_fontsize.y, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1174 average_accuracy = 0;
1175 int weapons_with_stats = 0;
1177 pos.x += weapon_width / 2;
1179 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1182 Accuracy_LoadColors();
1184 float oldposx = pos.x;
1188 FOREACH(Weapons, it != WEP_Null, {
1189 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1191 WepSet set = it.m_wepset;
1192 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1196 if (weapon_stats >= 0)
1197 weapon_alpha = sbt_fg_alpha;
1199 weapon_alpha = 0.2 * sbt_fg_alpha;
1202 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1204 if (weapon_stats >= 0) {
1205 weapons_with_stats += 1;
1206 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1209 s = sprintf("%d%%", weapon_stats * 100);
1212 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1214 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1215 rgb = Accuracy_GetColor(weapon_stats);
1217 drawstring(tmpos + eX * padding + eY * weapon_height, s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1219 tmpos.x += weapon_width * rows;
1220 pos.x += weapon_width * rows;
1221 if (rows == 2 && column == columnns - 1) {
1229 if (weapons_with_stats)
1230 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1232 panel_size.x += panel_bg_padding * 2; // restore initial width
1236 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1238 pos.x += hud_fontsize.x * 0.25;
1239 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1240 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1241 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1243 pos.y += hud_fontsize.y;
1248 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1249 float stat_secrets_found, stat_secrets_total;
1250 float stat_monsters_killed, stat_monsters_total;
1254 // get monster stats
1255 stat_monsters_killed = STAT(MONSTERS_KILLED);
1256 stat_monsters_total = STAT(MONSTERS_TOTAL);
1258 // get secrets stats
1259 stat_secrets_found = STAT(SECRETS_FOUND);
1260 stat_secrets_total = STAT(SECRETS_TOTAL);
1262 // get number of rows
1263 if(stat_secrets_total)
1265 if(stat_monsters_total)
1268 // if no rows, return
1272 // draw table header
1273 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1274 pos.y += 1.25 * hud_fontsize.y;
1275 if(panel.current_panel_bg != "0")
1276 pos.y += panel_bg_border;
1279 panel_size.y = hud_fontsize.y * rows;
1280 panel_size.y += panel_bg_padding * 2;
1283 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1284 if(panel.current_panel_bg != "0")
1285 end_pos.y += panel_bg_border * 2;
1287 if(panel_bg_padding)
1289 panel_pos += '1 1 0' * panel_bg_padding;
1290 panel_size -= '2 2 0' * panel_bg_padding;
1294 vector tmp = panel_size;
1297 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1300 if(stat_monsters_total)
1302 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1303 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1307 if(stat_secrets_total)
1309 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1310 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1313 panel_size.x += panel_bg_padding * 2; // restore initial width
1318 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1321 RANKINGS_RECEIVED_CNT = 0;
1322 for (i=RANKINGS_CNT-1; i>=0; --i)
1324 ++RANKINGS_RECEIVED_CNT;
1326 if (RANKINGS_RECEIVED_CNT == 0)
1329 vector hl_rgb = rgb + '0.5 0.5 0.5';
1331 pos.y += hud_fontsize.y;
1332 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1333 pos.y += 1.25 * hud_fontsize.y;
1334 if(panel.current_panel_bg != "0")
1335 pos.y += panel_bg_border;
1338 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1339 panel_size.y += panel_bg_padding * 2;
1342 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1343 if(panel.current_panel_bg != "0")
1344 end_pos.y += panel_bg_border * 2;
1346 if(panel_bg_padding)
1348 panel_pos += '1 1 0' * panel_bg_padding;
1349 panel_size -= '2 2 0' * panel_bg_padding;
1353 vector tmp = panel_size;
1356 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1359 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1366 n = grecordholder[i];
1367 p = count_ordinal(i+1);
1368 if(grecordholder[i] == entcs_GetName(player_localnum))
1369 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1370 else if(!(i % 2) && sbt_highlight)
1371 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1372 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1373 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);
1374 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1375 pos.y += 1.25 * hud_fontsize.y;
1378 panel_size.x += panel_bg_padding * 2; // restore initial width
1382 void Scoreboard_Draw()
1384 if(!autocvar__hud_configure)
1386 if(!hud_draw_maximized) return;
1388 // frametime checks allow to toggle the scoreboard even when the game is paused
1389 if(scoreboard_active) {
1390 if(hud_configure_menu_open == 1)
1391 scoreboard_fade_alpha = 1;
1392 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1393 if (scoreboard_fadeinspeed && frametime)
1394 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1396 scoreboard_fade_alpha = 1;
1397 if(hud_fontsize_str != autocvar_hud_fontsize)
1399 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1400 Scoreboard_initFieldSizes();
1401 if(hud_fontsize_str)
1402 strunzone(hud_fontsize_str);
1403 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1407 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1408 if (scoreboard_fadeoutspeed && frametime)
1409 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1411 scoreboard_fade_alpha = 0;
1414 if (!scoreboard_fade_alpha)
1418 scoreboard_fade_alpha = 0;
1420 if (autocvar_hud_panel_scoreboard_dynamichud)
1423 HUD_Scale_Disable();
1425 if(scoreboard_fade_alpha <= 0)
1427 panel_fade_alpha *= scoreboard_fade_alpha;
1428 HUD_Panel_LoadCvars();
1430 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1431 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1432 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1433 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1434 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1435 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1437 // don't overlap with con_notify
1438 if(!autocvar__hud_configure)
1439 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1441 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1442 float fixed_scoreboard_width = bound(vid_conwidth * 0.4, vid_conwidth - excess, vid_conwidth * 0.93);
1443 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1444 panel_size.x = fixed_scoreboard_width;
1446 Scoreboard_UpdatePlayerTeams();
1448 vector pos = panel_pos;
1453 vector sb_heading_fontsize;
1454 sb_heading_fontsize = hud_fontsize * 2;
1455 draw_beginBoldFont();
1456 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1459 pos.y += sb_heading_fontsize.y;
1460 if(panel.current_panel_bg != "0")
1461 pos.y += panel_bg_border;
1463 // Draw the scoreboard
1464 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1467 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1471 vector panel_bg_color_save = panel_bg_color;
1472 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1473 if(panel.current_panel_bg != "0")
1474 team_score_baseoffset.x -= panel_bg_border;
1475 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1477 if(tm.team == NUM_SPECTATOR)
1482 draw_beginBoldFont();
1483 vector rgb = Team_ColorRGB(tm.team);
1484 str = ftos(tm.(teamscores(ts_primary)));
1485 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1487 if(ts_primary != ts_secondary)
1489 str = ftos(tm.(teamscores(ts_secondary)));
1490 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);
1493 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1494 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1495 else if(panel_bg_color_team > 0)
1496 panel_bg_color = rgb * panel_bg_color_team;
1498 panel_bg_color = rgb;
1499 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1501 panel_bg_color = panel_bg_color_save;
1505 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1506 if(tm.team != NUM_SPECTATOR)
1508 // display it anyway
1509 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1512 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1513 if(race_speedaward) {
1514 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);
1515 pos.y += 1.25 * hud_fontsize.y;
1517 if(race_speedaward_alltimebest) {
1518 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);
1519 pos.y += 1.25 * hud_fontsize.y;
1521 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1523 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1524 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1526 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1529 for(pl = players.sort_next; pl; pl = pl.sort_next)
1531 if(pl.team == NUM_SPECTATOR)
1533 draw_beginBoldFont();
1534 drawstring(pos, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1536 pos.y += 1.25 * hud_fontsize.y;
1538 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1539 pos.y += 1.25 * hud_fontsize.y;
1545 // Print info string
1547 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1548 tl = STAT(TIMELIMIT);
1549 fl = STAT(FRAGLIMIT);
1550 ll = STAT(LEADLIMIT);
1551 if(gametype == MAPINFO_TYPE_LMS)
1554 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1559 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1563 str = strcat(str, _(" or"));
1566 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1567 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1568 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1569 TranslateScoresLabel(teamscores_label(ts_primary))));
1573 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1574 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1575 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1576 TranslateScoresLabel(scores_label(ps_primary))));
1581 if(tl > 0 || fl > 0)
1582 str = strcat(str, _(" or"));
1585 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1586 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1587 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1588 TranslateScoresLabel(teamscores_label(ts_primary))));
1592 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1593 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1594 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1595 TranslateScoresLabel(scores_label(ps_primary))));
1600 pos.y += 1.2 * hud_fontsize.y;
1601 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1603 // print information about respawn status
1604 float respawn_time = STAT(RESPAWN_TIME);
1608 if(respawn_time < 0)
1610 // a negative number means we are awaiting respawn, time value is still the same
1611 respawn_time *= -1; // remove mark now that we checked it
1613 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1614 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1616 str = sprintf(_("^1Respawning in ^3%s^1..."),
1617 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1618 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1620 count_seconds(ceil(respawn_time - time))
1624 else if(time < respawn_time)
1626 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1627 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1628 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1630 count_seconds(ceil(respawn_time - time))
1634 else if(time >= respawn_time)
1635 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1637 pos.y += 1.2 * hud_fontsize.y;
1638 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1641 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;