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;
25 float sbt_fg_alpha_self;
27 float sbt_highlight_alpha;
28 float sbt_highlight_alpha_self;
30 // provide basic panel cvars to old clients
31 // TODO remove them after a future release (0.8.2+)
32 string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
33 string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
34 string autocvar_hud_panel_scoreboard_bg = "border_default";
35 string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
36 string autocvar_hud_panel_scoreboard_bg_color_team = "";
37 string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
38 string autocvar_hud_panel_scoreboard_bg_border = "";
39 string autocvar_hud_panel_scoreboard_bg_padding = "";
41 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
42 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
43 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
44 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
45 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
46 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
47 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
48 bool autocvar_hud_panel_scoreboard_table_highlight = true;
49 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
50 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
51 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
53 bool autocvar_hud_panel_scoreboard_accuracy = true;
54 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
55 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
57 bool autocvar_hud_panel_scoreboard_dynamichud = false;
60 void drawstringright(vector, string, vector, vector, float, float);
61 void drawstringcenter(vector, string, vector, vector, float, float);
63 // wrapper to put all possible scores titles through gettext
64 string TranslateScoresLabel(string l)
68 case "bckills": return CTX(_("SCO^bckills"));
69 case "bctime": return CTX(_("SCO^bctime"));
70 case "caps": return CTX(_("SCO^caps"));
71 case "captime": return CTX(_("SCO^captime"));
72 case "deaths": return CTX(_("SCO^deaths"));
73 case "destroyed": return CTX(_("SCO^destroyed"));
74 case "dmg": return CTX(_("SCO^damage"));
75 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
76 case "drops": return CTX(_("SCO^drops"));
77 case "faults": return CTX(_("SCO^faults"));
78 case "fckills": return CTX(_("SCO^fckills"));
79 case "goals": return CTX(_("SCO^goals"));
80 case "kckills": return CTX(_("SCO^kckills"));
81 case "kdratio": return CTX(_("SCO^kdratio"));
82 case "kd": return CTX(_("SCO^k/d"));
83 case "kdr": return CTX(_("SCO^kdr"));
84 case "kills": return CTX(_("SCO^kills"));
85 case "laps": return CTX(_("SCO^laps"));
86 case "lives": return CTX(_("SCO^lives"));
87 case "losses": return CTX(_("SCO^losses"));
88 case "name": return CTX(_("SCO^name"));
89 case "sum": return CTX(_("SCO^sum"));
90 case "nick": return CTX(_("SCO^nick"));
91 case "objectives": return CTX(_("SCO^objectives"));
92 case "pickups": return CTX(_("SCO^pickups"));
93 case "ping": return CTX(_("SCO^ping"));
94 case "pl": return CTX(_("SCO^pl"));
95 case "pushes": return CTX(_("SCO^pushes"));
96 case "rank": return CTX(_("SCO^rank"));
97 case "returns": return CTX(_("SCO^returns"));
98 case "revivals": return CTX(_("SCO^revivals"));
99 case "rounds": return CTX(_("SCO^rounds won"));
100 case "score": return CTX(_("SCO^score"));
101 case "suicides": return CTX(_("SCO^suicides"));
102 case "takes": return CTX(_("SCO^takes"));
103 case "ticks": return CTX(_("SCO^ticks"));
108 void Scoreboard_InitScores()
112 ps_primary = ps_secondary = NULL;
113 ts_primary = ts_secondary = -1;
114 FOREACH(Scores, true, {
115 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
116 if(f == SFL_SORT_PRIO_PRIMARY)
118 if(f == SFL_SORT_PRIO_SECONDARY)
121 if(ps_secondary == NULL)
122 ps_secondary = ps_primary;
124 for(i = 0; i < MAX_TEAMSCORE; ++i)
126 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
127 if(f == SFL_SORT_PRIO_PRIMARY)
129 if(f == SFL_SORT_PRIO_SECONDARY)
132 if(ts_secondary == -1)
133 ts_secondary = ts_primary;
135 Cmd_Scoreboard_SetFields(0);
138 float SetTeam(entity pl, float Team);
140 void Scoreboard_UpdatePlayerTeams()
145 for(pl = players.sort_next; pl; pl = pl.sort_next)
148 Team = entcs_GetScoreTeam(pl.sv_entnum);
149 if(SetTeam(pl, Team))
152 Scoreboard_UpdatePlayerPos(pl);
156 pl = players.sort_next;
161 print(strcat("PNUM: ", ftos(num), "\n"));
166 int Scoreboard_CompareScore(int vl, int vr, int f)
168 TC(int, vl); TC(int, vr); TC(int, f);
169 if(f & SFL_ZERO_IS_WORST)
171 if(vl == 0 && vr != 0)
173 if(vl != 0 && vr == 0)
177 return IS_INCREASING(f);
179 return IS_DECREASING(f);
183 float Scoreboard_ComparePlayerScores(entity left, entity right)
186 vl = entcs_GetTeam(left.sv_entnum);
187 vr = entcs_GetTeam(right.sv_entnum);
199 if(vl == NUM_SPECTATOR)
201 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
203 if(!left.gotscores && right.gotscores)
208 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
212 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
216 FOREACH(Scores, true, {
217 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
218 if (r >= 0) return r;
221 if (left.sv_entnum < right.sv_entnum)
227 void Scoreboard_UpdatePlayerPos(entity player)
230 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
232 SORT_SWAP(player, ent);
234 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
236 SORT_SWAP(ent, player);
240 float Scoreboard_CompareTeamScores(entity left, entity right)
244 if(left.team == NUM_SPECTATOR)
246 if(right.team == NUM_SPECTATOR)
249 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
253 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
257 for(i = 0; i < MAX_TEAMSCORE; ++i)
259 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
264 if (left.team < right.team)
270 void Scoreboard_UpdateTeamPos(entity Team)
273 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
275 SORT_SWAP(Team, ent);
277 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
279 SORT_SWAP(ent, Team);
283 void Cmd_Scoreboard_Help()
285 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
286 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
287 LOG_INFO(_("Usage:\n"));
288 LOG_INFO(_("^2scoreboard_columns_set default\n"));
289 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
290 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
291 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
294 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
295 LOG_INFO(_("^3ping^7 Ping time\n"));
296 LOG_INFO(_("^3pl^7 Packet loss\n"));
297 LOG_INFO(_("^3elo^7 Player ELO\n"));
298 LOG_INFO(_("^3kills^7 Number of kills\n"));
299 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
300 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
301 LOG_INFO(_("^3frags^7 kills - suicides\n"));
302 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
303 LOG_INFO(_("^3dmg^7 The total damage done\n"));
304 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
305 LOG_INFO(_("^3sum^7 frags - deaths\n"));
306 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
307 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
308 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
309 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
310 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
311 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
312 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
313 LOG_INFO(_("^3rank^7 Player rank\n"));
314 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
315 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
316 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
317 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
318 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
319 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
320 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
321 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
322 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
323 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
324 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
325 LOG_INFO(_("^3score^7 Total score\n"));
328 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
329 "of game types, then a slash, to make the field show up only in these\n"
330 "or in all but these game types. You can also specify 'all' as a\n"
331 "field to show all fields available for the current game mode.\n\n"));
333 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
334 "include/exclude ALL teams/noteams game modes.\n\n"));
336 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
337 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
338 "right of the vertical bar aligned to the right.\n"));
339 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
340 "other gamemodes except DM.\n"));
343 // NOTE: adding a gametype with ? to not warn for an optional field
344 // make sure it's excluded in a previous exclusive rule, if any
345 // otherwise the previous exclusive rule warns anyway
346 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
347 #define SCOREBOARD_DEFAULT_COLUMNS \
349 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
350 " -teams,lms/deaths +ft,tdm/deaths" \
351 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
352 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
353 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
354 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
355 " +lms/lives +lms/rank" \
356 " +kh/caps +kh/pushes +kh/destroyed" \
357 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
358 " +as/objectives +nb/faults +nb/goals" \
359 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
360 " -lms,rc,cts,inv,nb/score"
362 void Cmd_Scoreboard_SetFields(int argc)
367 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
371 return; // do nothing, we don't know gametype and scores yet
373 // sbt_fields uses strunzone on the titles!
374 if(!sbt_field_title[0])
375 for(i = 0; i < MAX_SBT_FIELDS; ++i)
376 sbt_field_title[i] = strzone("(null)");
378 // TODO: re enable with gametype dependant cvars?
379 if(argc < 3) // no arguments provided
380 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
383 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
387 if(argv(2) == "default")
388 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
389 else if(argv(2) == "all")
392 s = "ping pl name |";
393 FOREACH(Scores, true, {
395 if(it != ps_secondary)
396 if(scores_label(it) != "")
397 s = strcat(s, " ", scores_label(it));
399 if(ps_secondary != ps_primary)
400 s = strcat(s, " ", scores_label(ps_secondary));
401 s = strcat(s, " ", scores_label(ps_primary));
402 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
409 hud_fontsize = HUD_GetFontsize("hud_fontsize");
411 for(i = 1; i < argc - 1; ++i)
417 if(substring(str, 0, 1) == "?")
420 str = substring(str, 1, strlen(str) - 1);
423 slash = strstrofs(str, "/", 0);
426 pattern = substring(str, 0, slash);
427 str = substring(str, slash + 1, strlen(str) - (slash + 1));
429 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
433 strunzone(sbt_field_title[sbt_num_fields]);
434 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
435 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
436 str = strtolower(str);
441 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
442 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
443 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
444 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
445 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
446 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
447 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
448 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
449 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
452 FOREACH(Scores, true, {
453 if (str == strtolower(scores_label(it))) {
455 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
465 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
469 sbt_field[sbt_num_fields] = j;
472 if(j == ps_secondary)
473 have_secondary = true;
478 if(sbt_num_fields >= MAX_SBT_FIELDS)
482 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
484 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
485 have_secondary = true;
486 if(ps_primary == ps_secondary)
487 have_secondary = true;
488 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
490 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
494 strunzone(sbt_field_title[sbt_num_fields]);
495 for(i = sbt_num_fields; i > 0; --i)
497 sbt_field_title[i] = sbt_field_title[i-1];
498 sbt_field_size[i] = sbt_field_size[i-1];
499 sbt_field[i] = sbt_field[i-1];
501 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
502 sbt_field[0] = SP_NAME;
504 LOG_INFO("fixed missing field 'name'\n");
508 strunzone(sbt_field_title[sbt_num_fields]);
509 for(i = sbt_num_fields; i > 1; --i)
511 sbt_field_title[i] = sbt_field_title[i-1];
512 sbt_field_size[i] = sbt_field_size[i-1];
513 sbt_field[i] = sbt_field[i-1];
515 sbt_field_title[1] = strzone("|");
516 sbt_field[1] = SP_SEPARATOR;
517 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
519 LOG_INFO("fixed missing field '|'\n");
522 else if(!have_separator)
524 strunzone(sbt_field_title[sbt_num_fields]);
525 sbt_field_title[sbt_num_fields] = strzone("|");
526 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
527 sbt_field[sbt_num_fields] = SP_SEPARATOR;
529 LOG_INFO("fixed missing field '|'\n");
533 strunzone(sbt_field_title[sbt_num_fields]);
534 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
535 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
536 sbt_field[sbt_num_fields] = ps_secondary;
538 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
542 strunzone(sbt_field_title[sbt_num_fields]);
543 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
544 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
545 sbt_field[sbt_num_fields] = ps_primary;
547 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
551 sbt_field[sbt_num_fields] = SP_END;
555 vector sbt_field_rgb;
556 string sbt_field_icon0;
557 string sbt_field_icon1;
558 string sbt_field_icon2;
559 vector sbt_field_icon0_rgb;
560 vector sbt_field_icon1_rgb;
561 vector sbt_field_icon2_rgb;
562 string Scoreboard_GetField(entity pl, PlayerScoreField field)
564 float tmp, num, denom;
567 sbt_field_rgb = '1 1 1';
568 sbt_field_icon0 = "";
569 sbt_field_icon1 = "";
570 sbt_field_icon2 = "";
571 sbt_field_icon0_rgb = '1 1 1';
572 sbt_field_icon1_rgb = '1 1 1';
573 sbt_field_icon2_rgb = '1 1 1';
578 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
579 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
583 tmp = max(0, min(220, f-80)) / 220;
584 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
590 f = pl.ping_packetloss;
591 tmp = pl.ping_movementloss;
592 if(f == 0 && tmp == 0)
594 str = ftos(ceil(f * 100));
596 str = strcat(str, "~", ftos(ceil(tmp * 100)));
597 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
598 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
602 if(ready_waiting && pl.ready)
604 sbt_field_icon0 = "gfx/scoreboard/player_ready";
608 f = entcs_GetClientColors(pl.sv_entnum);
610 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
611 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
612 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
613 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
614 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
617 return entcs_GetName(pl.sv_entnum);
620 f = pl.(scores(SP_KILLS));
621 f -= pl.(scores(SP_SUICIDES));
625 num = pl.(scores(SP_KILLS));
626 denom = pl.(scores(SP_DEATHS));
629 sbt_field_rgb = '0 1 0';
630 str = sprintf("%d", num);
631 } else if(num <= 0) {
632 sbt_field_rgb = '1 0 0';
633 str = sprintf("%.1f", num/denom);
635 str = sprintf("%.1f", num/denom);
639 f = pl.(scores(SP_KILLS));
640 f -= pl.(scores(SP_DEATHS));
643 sbt_field_rgb = '0 1 0';
645 sbt_field_rgb = '1 1 1';
647 sbt_field_rgb = '1 0 0';
653 float elo = pl.(scores(SP_ELO));
655 case -1: return "...";
656 case -2: return _("N/A");
657 default: return ftos(elo);
661 case SP_DMG: case SP_DMGTAKEN:
662 return sprintf("%.1f k", pl.(scores(field)) / 1000);
665 tmp = pl.(scores(field));
666 f = scores_flags(field);
667 if(field == ps_primary)
668 sbt_field_rgb = '1 1 0';
669 else if(field == ps_secondary)
670 sbt_field_rgb = '0 1 1';
672 sbt_field_rgb = '1 1 1';
673 return ScoreString(f, tmp);
678 float sbt_fixcolumnwidth_len;
679 float sbt_fixcolumnwidth_iconlen;
680 float sbt_fixcolumnwidth_marginlen;
682 string Scoreboard_FixColumnWidth(int i, string str)
688 sbt_fixcolumnwidth_iconlen = 0;
690 if(sbt_field_icon0 != "")
692 sz = draw_getimagesize(sbt_field_icon0);
694 if(sbt_fixcolumnwidth_iconlen < f)
695 sbt_fixcolumnwidth_iconlen = f;
698 if(sbt_field_icon1 != "")
700 sz = draw_getimagesize(sbt_field_icon1);
702 if(sbt_fixcolumnwidth_iconlen < f)
703 sbt_fixcolumnwidth_iconlen = f;
706 if(sbt_field_icon2 != "")
708 sz = draw_getimagesize(sbt_field_icon2);
710 if(sbt_fixcolumnwidth_iconlen < f)
711 sbt_fixcolumnwidth_iconlen = f;
714 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
716 if(sbt_fixcolumnwidth_iconlen != 0)
717 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
719 sbt_fixcolumnwidth_marginlen = 0;
721 if(sbt_field[i] == SP_NAME) // name gets all remaining space
725 namesize = panel_size.x;
726 for(j = 0; j < sbt_num_fields; ++j)
728 if (sbt_field[i] != SP_SEPARATOR)
729 namesize -= sbt_field_size[j] + hud_fontsize.x;
730 sbt_field_size[i] = namesize;
732 if (sbt_fixcolumnwidth_iconlen != 0)
733 namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
734 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
735 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
738 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
740 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
741 if(sbt_field_size[i] < f)
742 sbt_field_size[i] = f;
747 void Scoreboard_initFieldSizes()
749 for(int i = 0; i < sbt_num_fields; ++i)
750 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
753 vector Scoreboard_DrawHeader(vector pos, vector rgb)
756 vector column_dim = eY * panel_size.y;
757 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
758 pos.x += hud_fontsize.x * 0.5;
759 for(i = 0; i < sbt_num_fields; ++i)
761 if(sbt_field[i] == SP_SEPARATOR)
763 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
766 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
767 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
768 pos.x += column_dim.x;
770 if(sbt_field[i] == SP_SEPARATOR)
772 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
773 for(i = sbt_num_fields - 1; i > 0; --i)
775 if(sbt_field[i] == SP_SEPARATOR)
778 pos.x -= sbt_field_size[i];
783 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
784 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
787 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
788 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
789 pos.x -= hud_fontsize.x;
794 pos.y += 1.25 * hud_fontsize.y;
798 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
800 TC(bool, is_self); TC(int, pl_number);
802 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
804 vector h_pos = item_pos;
805 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
806 // alternated rows highlighting
808 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
809 else if((sbt_highlight) && (!(pl_number % 2)))
810 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
812 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
814 vector pos = item_pos;
815 pos.x += hud_fontsize.x * 0.5;
816 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
817 vector tmp = '0 0 0';
819 PlayerScoreField field;
820 for(i = 0; i < sbt_num_fields; ++i)
822 field = sbt_field[i];
823 if(field == SP_SEPARATOR)
826 if(is_spec && field != SP_NAME && field != SP_PING) {
827 pos.x += sbt_field_size[i] + hud_fontsize.x;
830 str = Scoreboard_GetField(pl, field);
831 str = Scoreboard_FixColumnWidth(i, str);
833 pos.x += sbt_field_size[i] + hud_fontsize.x;
835 if(field == SP_NAME) {
836 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
837 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
839 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
840 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
843 tmp.x = sbt_field_size[i] + hud_fontsize.x;
844 if(sbt_field_icon0 != "")
845 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);
846 if(sbt_field_icon1 != "")
847 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);
848 if(sbt_field_icon2 != "")
849 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);
852 if(sbt_field[i] == SP_SEPARATOR)
854 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
855 for(i = sbt_num_fields-1; i > 0; --i)
857 field = sbt_field[i];
858 if(field == SP_SEPARATOR)
861 if(is_spec && field != SP_NAME && field != SP_PING) {
862 pos.x -= sbt_field_size[i] + hud_fontsize.x;
866 str = Scoreboard_GetField(pl, field);
867 str = Scoreboard_FixColumnWidth(i, str);
869 if(field == SP_NAME) {
870 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
871 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
873 tmp.x = sbt_fixcolumnwidth_len;
874 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
877 tmp.x = sbt_field_size[i];
878 if(sbt_field_icon0 != "")
879 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);
880 if(sbt_field_icon1 != "")
881 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);
882 if(sbt_field_icon2 != "")
883 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);
884 pos.x -= sbt_field_size[i] + hud_fontsize.x;
889 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
892 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
897 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
898 panel_size.y += panel_bg_padding * 2;
901 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
902 if(panel.current_panel_bg != "0")
903 end_pos.y += panel_bg_border * 2;
907 panel_pos += '1 1 0' * panel_bg_padding;
908 panel_size -= '2 2 0' * panel_bg_padding;
912 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
916 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
918 pos.y += 1.25 * hud_fontsize.y;
921 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
923 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
926 // print header row and highlight columns
927 pos = Scoreboard_DrawHeader(panel_pos, rgb);
929 // fill the table and draw the rows
932 for(pl = players.sort_next; pl; pl = pl.sort_next)
934 if(pl.team != tm.team)
936 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
937 pos.y += 1.25 * hud_fontsize.y;
941 for(pl = players.sort_next; pl; pl = pl.sort_next)
943 if(pl.team == NUM_SPECTATOR)
945 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
946 pos.y += 1.25 * hud_fontsize.y;
950 panel_size.x += panel_bg_padding * 2; // restore initial width
954 bool Scoreboard_WouldDraw()
956 if (QuickMenu_IsOpened())
958 else if (HUD_Radar_Clickable())
960 else if (scoreboard_showscores)
962 else if (intermission == 1)
964 else if (intermission == 2)
966 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
968 else if (scoreboard_showscores_force)
973 float average_accuracy;
974 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
976 WepSet weapons_stat = WepSet_GetFromStat();
977 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
980 FOREACH(Weapons, it != WEP_Null, {
981 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
983 WepSet set = it.m_wepset;
984 if (weapon_stats < 0)
986 if (!(weapons_stat & set) && (it.spawnflags & WEP_FLAG_HIDDEN || it.spawnflags & WEP_FLAG_MUTATORBLOCKED))
988 else if (!(weapons_stat & set || weapons_inmap & set))
993 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
994 if (weapon_cnt <= 0) return pos;
997 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
999 int columnns = ceil(weapon_cnt / rows);
1001 float weapon_height = 29;
1002 float height = hud_fontsize.y + weapon_height;
1004 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1005 pos.y += 1.25 * hud_fontsize.y;
1006 if(panel.current_panel_bg != "0")
1007 pos.y += panel_bg_border;
1010 panel_size.y = height * rows;
1011 panel_size.y += panel_bg_padding * 2;
1014 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1015 if(panel.current_panel_bg != "0")
1016 end_pos.y += panel_bg_border * 2;
1018 if(panel_bg_padding)
1020 panel_pos += '1 1 0' * panel_bg_padding;
1021 panel_size -= '2 2 0' * panel_bg_padding;
1025 vector tmp = panel_size;
1027 float weapon_width = tmp.x / columnns / rows;
1030 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1034 // column highlighting
1035 for (int i = 0; i < columnns; ++i)
1037 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1040 for (int i = 0; i < rows; ++i)
1041 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * hud_fontsize.y, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1044 average_accuracy = 0;
1045 int weapons_with_stats = 0;
1047 pos.x += weapon_width / 2;
1049 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1052 Accuracy_LoadColors();
1054 float oldposx = pos.x;
1058 FOREACH(Weapons, it != WEP_Null, {
1059 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1061 WepSet set = it.m_wepset;
1062 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1066 if (weapon_stats >= 0)
1067 weapon_alpha = sbt_fg_alpha;
1069 weapon_alpha = 0.2 * sbt_fg_alpha;
1072 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1074 if (weapon_stats >= 0) {
1075 weapons_with_stats += 1;
1076 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1079 s = sprintf("%d%%", weapon_stats * 100);
1082 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1084 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1085 rgb = Accuracy_GetColor(weapon_stats);
1087 drawstring(tmpos + eX * padding + eY * weapon_height, s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1089 tmpos.x += weapon_width * rows;
1090 pos.x += weapon_width * rows;
1091 if (rows == 2 && column == columnns - 1) {
1099 if (weapons_with_stats)
1100 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1102 panel_size.x += panel_bg_padding * 2; // restore initial width
1106 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1108 pos.x += hud_fontsize.x * 0.25;
1109 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1110 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1111 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1113 pos.y += hud_fontsize.y;
1118 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1119 float stat_secrets_found, stat_secrets_total;
1120 float stat_monsters_killed, stat_monsters_total;
1124 // get monster stats
1125 stat_monsters_killed = STAT(MONSTERS_KILLED);
1126 stat_monsters_total = STAT(MONSTERS_TOTAL);
1128 // get secrets stats
1129 stat_secrets_found = STAT(SECRETS_FOUND);
1130 stat_secrets_total = STAT(SECRETS_TOTAL);
1132 // get number of rows
1133 if(stat_secrets_total)
1135 if(stat_monsters_total)
1138 // if no rows, return
1142 // draw table header
1143 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1144 pos.y += 1.25 * hud_fontsize.y;
1145 if(panel.current_panel_bg != "0")
1146 pos.y += panel_bg_border;
1149 panel_size.y = hud_fontsize.y * rows;
1150 panel_size.y += panel_bg_padding * 2;
1153 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1154 if(panel.current_panel_bg != "0")
1155 end_pos.y += panel_bg_border * 2;
1157 if(panel_bg_padding)
1159 panel_pos += '1 1 0' * panel_bg_padding;
1160 panel_size -= '2 2 0' * panel_bg_padding;
1164 vector tmp = panel_size;
1167 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1170 if(stat_monsters_total)
1172 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1173 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1177 if(stat_secrets_total)
1179 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1180 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1183 panel_size.x += panel_bg_padding * 2; // restore initial width
1188 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1191 RANKINGS_RECEIVED_CNT = 0;
1192 for (i=RANKINGS_CNT-1; i>=0; --i)
1194 ++RANKINGS_RECEIVED_CNT;
1196 if (RANKINGS_RECEIVED_CNT == 0)
1199 vector hl_rgb = rgb + '0.5 0.5 0.5';
1201 pos.y += hud_fontsize.y;
1202 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1203 pos.y += 1.25 * hud_fontsize.y;
1204 if(panel.current_panel_bg != "0")
1205 pos.y += panel_bg_border;
1208 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1209 panel_size.y += panel_bg_padding * 2;
1212 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1213 if(panel.current_panel_bg != "0")
1214 end_pos.y += panel_bg_border * 2;
1216 if(panel_bg_padding)
1218 panel_pos += '1 1 0' * panel_bg_padding;
1219 panel_size -= '2 2 0' * panel_bg_padding;
1223 vector tmp = panel_size;
1226 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1229 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1236 n = grecordholder[i];
1237 p = count_ordinal(i+1);
1238 if(grecordholder[i] == entcs_GetName(player_localnum))
1239 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1240 else if(!(i % 2) && sbt_highlight)
1241 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1242 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1243 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);
1244 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1245 pos.y += 1.25 * hud_fontsize.y;
1248 panel_size.x += panel_bg_padding * 2; // restore initial width
1252 void Scoreboard_Draw()
1254 if(!autocvar__hud_configure)
1256 if(!hud_draw_maximized) return;
1258 // frametime checks allow to toggle the scoreboard even when the game is paused
1259 if(scoreboard_active) {
1260 if(hud_configure_menu_open == 1)
1261 scoreboard_fade_alpha = 1;
1262 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1263 if (scoreboard_fadeinspeed && frametime)
1264 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1266 scoreboard_fade_alpha = 1;
1267 if(hud_fontsize_str != autocvar_hud_fontsize)
1269 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1270 Scoreboard_initFieldSizes();
1271 if(hud_fontsize_str)
1272 strunzone(hud_fontsize_str);
1273 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1277 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1278 if (scoreboard_fadeoutspeed && frametime)
1279 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1281 scoreboard_fade_alpha = 0;
1284 if (!scoreboard_fade_alpha)
1288 scoreboard_fade_alpha = 0;
1290 if (autocvar_hud_panel_scoreboard_dynamichud)
1293 HUD_Scale_Disable();
1295 if(scoreboard_fade_alpha <= 0)
1297 panel_fade_alpha *= scoreboard_fade_alpha;
1298 HUD_Panel_LoadCvars();
1300 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1301 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1302 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1303 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1304 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1305 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1307 // don't overlap with con_notify
1308 if(!autocvar__hud_configure)
1309 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1311 Scoreboard_UpdatePlayerTeams();
1317 // Initializes position
1321 vector sb_heading_fontsize;
1322 sb_heading_fontsize = hud_fontsize * 2;
1323 draw_beginBoldFont();
1324 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1327 pos.y += sb_heading_fontsize.y;
1328 if(panel.current_panel_bg != "0")
1329 pos.y += panel_bg_border;
1331 // Draw the scoreboard
1332 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1335 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1339 vector panel_bg_color_save = panel_bg_color;
1340 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1341 if(panel.current_panel_bg != "0")
1342 team_score_baseoffset.x -= panel_bg_border;
1343 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1345 if(tm.team == NUM_SPECTATOR)
1350 draw_beginBoldFont();
1351 vector rgb = Team_ColorRGB(tm.team);
1352 str = ftos(tm.(teamscores(ts_primary)));
1353 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1355 if(ts_primary != ts_secondary)
1357 str = ftos(tm.(teamscores(ts_secondary)));
1358 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);
1361 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1362 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1363 else if(panel_bg_color_team > 0)
1364 panel_bg_color = rgb * panel_bg_color_team;
1366 panel_bg_color = rgb;
1367 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1369 panel_bg_color = panel_bg_color_save;
1373 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1375 if(tm.team == NUM_SPECTATOR)
1378 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1382 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1383 if(race_speedaward) {
1384 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);
1385 pos.y += 1.25 * hud_fontsize.y;
1387 if(race_speedaward_alltimebest) {
1388 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);
1389 pos.y += 1.25 * hud_fontsize.y;
1391 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1393 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1394 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1396 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1401 for(pl = players.sort_next; pl; pl = pl.sort_next)
1403 if(pl.team != NUM_SPECTATOR)
1405 pos.y += 1.25 * hud_fontsize.y;
1406 Scoreboard_DrawItem(pos, '0 0 0', pl, (pl.sv_entnum == player_localnum), specs);
1412 draw_beginBoldFont();
1413 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1415 pos.y += 1.25 * hud_fontsize.y;
1418 // Print info string
1420 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1421 tl = STAT(TIMELIMIT);
1422 fl = STAT(FRAGLIMIT);
1423 ll = STAT(LEADLIMIT);
1424 if(gametype == MAPINFO_TYPE_LMS)
1427 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1432 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1436 str = strcat(str, _(" or"));
1439 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1440 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1441 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1442 TranslateScoresLabel(teamscores_label(ts_primary))));
1446 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1447 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1448 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1449 TranslateScoresLabel(scores_label(ps_primary))));
1454 if(tl > 0 || fl > 0)
1455 str = strcat(str, _(" or"));
1458 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1459 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1460 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1461 TranslateScoresLabel(teamscores_label(ts_primary))));
1465 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1466 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1467 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1468 TranslateScoresLabel(scores_label(ps_primary))));
1473 pos.y += 1.2 * hud_fontsize.y;
1474 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1476 // print information about respawn status
1477 float respawn_time = STAT(RESPAWN_TIME);
1481 if(respawn_time < 0)
1483 // a negative number means we are awaiting respawn, time value is still the same
1484 respawn_time *= -1; // remove mark now that we checked it
1486 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1487 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1489 str = sprintf(_("^1Respawning in ^3%s^1..."),
1490 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1491 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1493 count_seconds(ceil(respawn_time - time))
1497 else if(time < respawn_time)
1499 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1500 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1501 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1503 count_seconds(ceil(respawn_time - time))
1507 else if(time >= respawn_time)
1508 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1510 pos.y += 1.2 * hud_fontsize.y;
1511 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1514 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;