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 float sbt_fg_alpha_self;
15 float sbt_highlight_alpha;
16 float sbt_highlight_alpha_self;
18 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
19 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
20 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
21 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0.7;
22 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
23 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
24 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
25 bool autocvar_hud_panel_scoreboard_table_highlight = true;
26 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
27 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.5;
28 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
30 bool autocvar_hud_panel_scoreboard_accuracy = true;
31 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
32 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
34 bool autocvar_hud_panel_scoreboard_dynamichud = false;
37 void drawstringright(vector, string, vector, vector, float, float);
38 void drawstringcenter(vector, string, vector, vector, float, float);
40 // wrapper to put all possible scores titles through gettext
41 string TranslateScoresLabel(string l)
45 case "bckills": return CTX(_("SCO^bckills"));
46 case "bctime": return CTX(_("SCO^bctime"));
47 case "caps": return CTX(_("SCO^caps"));
48 case "captime": return CTX(_("SCO^captime"));
49 case "deaths": return CTX(_("SCO^deaths"));
50 case "destroyed": return CTX(_("SCO^destroyed"));
51 case "dmg": return CTX(_("SCO^dmg"));
52 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
53 case "drops": return CTX(_("SCO^drops"));
54 case "faults": return CTX(_("SCO^faults"));
55 case "fckills": return CTX(_("SCO^fckills"));
56 case "goals": return CTX(_("SCO^goals"));
57 case "kckills": return CTX(_("SCO^kckills"));
58 case "kdratio": return CTX(_("SCO^kdratio"));
59 case "k/d": return CTX(_("SCO^k/d"));
60 case "kd": return CTX(_("SCO^kd"));
61 case "kdr": return CTX(_("SCO^kdr"));
62 case "kills": return CTX(_("SCO^kills"));
63 case "laps": return CTX(_("SCO^laps"));
64 case "lives": return CTX(_("SCO^lives"));
65 case "losses": return CTX(_("SCO^losses"));
66 case "name": return CTX(_("SCO^name"));
67 case "sum": return CTX(_("SCO^sum"));
68 case "nick": return CTX(_("SCO^nick"));
69 case "objectives": return CTX(_("SCO^objectives"));
70 case "pickups": return CTX(_("SCO^pickups"));
71 case "ping": return CTX(_("SCO^ping"));
72 case "pl": return CTX(_("SCO^pl"));
73 case "pushes": return CTX(_("SCO^pushes"));
74 case "rank": return CTX(_("SCO^rank"));
75 case "returns": return CTX(_("SCO^returns"));
76 case "revivals": return CTX(_("SCO^revivals"));
77 case "score": return CTX(_("SCO^score"));
78 case "suicides": return CTX(_("SCO^suicides"));
79 case "takes": return CTX(_("SCO^takes"));
80 case "ticks": return CTX(_("SCO^ticks"));
85 void Scoreboard_InitScores()
89 ps_primary = ps_secondary = ts_primary = ts_secondary = -1;
90 for(i = 0; i < MAX_SCORE; ++i)
92 f = (scores_flags[i] & SFL_SORT_PRIO_MASK);
93 if(f == SFL_SORT_PRIO_PRIMARY)
95 if(f == SFL_SORT_PRIO_SECONDARY)
98 if(ps_secondary == -1)
99 ps_secondary = ps_primary;
101 for(i = 0; i < MAX_TEAMSCORE; ++i)
103 f = (teamscores_flags[i] & SFL_SORT_PRIO_MASK);
104 if(f == SFL_SORT_PRIO_PRIMARY)
106 if(f == SFL_SORT_PRIO_SECONDARY)
109 if(ts_secondary == -1)
110 ts_secondary = ts_primary;
112 Cmd_Scoreboard_SetFields(0);
115 float SetTeam(entity pl, float Team);
117 void Scoreboard_UpdatePlayerTeams()
124 for(pl = players.sort_next; pl; pl = pl.sort_next)
127 Team = entcs_GetScoreTeam(pl.sv_entnum);
128 if(SetTeam(pl, Team))
131 Scoreboard_UpdatePlayerPos(pl);
135 pl = players.sort_next;
140 print(strcat("PNUM: ", ftos(num), "\n"));
145 int Scoreboard_CompareScore(int vl, int vr, int f)
147 TC(int, vl); TC(int, vr); TC(int, f);
148 if(f & SFL_ZERO_IS_WORST)
150 if(vl == 0 && vr != 0)
152 if(vl != 0 && vr == 0)
156 return IS_INCREASING(f);
158 return IS_DECREASING(f);
162 float Scoreboard_ComparePlayerScores(entity left, entity right)
165 vl = entcs_GetTeam(left.sv_entnum);
166 vr = entcs_GetTeam(right.sv_entnum);
178 if(vl == NUM_SPECTATOR)
180 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
182 if(!left.gotscores && right.gotscores)
187 r = Scoreboard_CompareScore(left.scores[ps_primary], right.scores[ps_primary], scores_flags[ps_primary]);
191 r = Scoreboard_CompareScore(left.scores[ps_secondary], right.scores[ps_secondary], scores_flags[ps_secondary]);
196 for(i = 0; i < MAX_SCORE; ++i)
198 r = Scoreboard_CompareScore(left.scores[i], right.scores[i], scores_flags[i]);
203 if (left.sv_entnum < right.sv_entnum)
209 void Scoreboard_UpdatePlayerPos(entity player)
212 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
214 SORT_SWAP(player, ent);
216 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
218 SORT_SWAP(ent, player);
222 float Scoreboard_CompareTeamScores(entity left, entity right)
226 if(left.team == NUM_SPECTATOR)
228 if(right.team == NUM_SPECTATOR)
231 r = Scoreboard_CompareScore(left.teamscores[ts_primary], right.teamscores[ts_primary], teamscores_flags[ts_primary]);
235 r = Scoreboard_CompareScore(left.teamscores[ts_secondary], right.teamscores[ts_secondary], teamscores_flags[ts_secondary]);
239 for(i = 0; i < MAX_SCORE; ++i)
241 r = Scoreboard_CompareScore(left.teamscores[i], right.teamscores[i], teamscores_flags[i]);
246 if (left.team < right.team)
252 void Scoreboard_UpdateTeamPos(entity Team)
255 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
257 SORT_SWAP(Team, ent);
259 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
261 SORT_SWAP(ent, Team);
265 void Cmd_Scoreboard_Help()
267 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
268 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
269 LOG_INFO(_("Usage:\n"));
270 LOG_INFO(_("^2scoreboard_columns_set default\n"));
271 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
272 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
273 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n\n"));
275 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
276 LOG_INFO(_("^3ping^7 Ping time\n"));
277 LOG_INFO(_("^3pl^7 Packet loss\n"));
278 LOG_INFO(_("^3kills^7 Number of kills\n"));
279 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
280 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
281 LOG_INFO(_("^3frags^7 kills - suicides\n"));
282 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
283 LOG_INFO(_("^3dmg^7 The total damage done\n"));
284 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
285 LOG_INFO(_("^3sum^7 frags - deaths\n"));
286 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
287 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
288 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
289 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
290 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
291 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
292 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
293 LOG_INFO(_("^3rank^7 Player rank\n"));
294 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
295 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
296 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
297 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
298 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
299 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
300 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
301 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
302 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
303 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
304 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
305 LOG_INFO(_("^3score^7 Total score\n\n"));
307 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
308 "of game types, then a slash, to make the field show up only in these\n"
309 "or in all but these game types. You can also specify 'all' as a\n"
310 "field to show all fields available for the current game mode.\n\n"));
312 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
313 "include/exclude ALL teams/noteams game modes.\n\n"));
315 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
316 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
317 "right of the vertical bar aligned to the right.\n"));
318 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
319 "other gamemodes except DM.\n"));
322 // NOTE: adding a gametype with ? to not warn for an optional field
323 // make sure it's excluded in a previous exclusive rule, if any
324 // otherwise the previous exclusive rule warns anyway
325 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
326 #define SCOREBOARD_DEFAULT_COLUMNS \
328 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
329 " -teams,lms/deaths +ft,tdm/deaths" \
330 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
331 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
332 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
333 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
334 " +lms/lives +lms/rank" \
335 " +kh/caps +kh/pushes +kh/destroyed" \
336 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
337 " +as/objectives +nb/faults +nb/goals" \
338 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
339 " -lms,rc,cts,inv,nb/score"
341 void Cmd_Scoreboard_SetFields(int argc)
346 float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
351 // set up a temporary scoreboard layout
352 // no layout can be properly set up until score_info data haven't been received
353 argc = tokenizebyseparator("0 1 ping pl name | score", " ");
355 scores_label[ps_primary] = strzone("score");
356 scores_flags[ps_primary] = SFL_ALLOW_HIDE;
359 // TODO: re enable with gametype dependant cvars?
360 if(argc < 3) // no arguments provided
361 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
364 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
368 if(argv(2) == "default")
369 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
370 else if(argv(2) == "all")
373 s = "ping pl name |";
374 for(i = 0; i < MAX_SCORE; ++i)
377 if(i != ps_secondary)
378 if(scores_label[i] != "")
379 s = strcat(s, " ", scores_label[i]);
381 if(ps_secondary != ps_primary)
382 s = strcat(s, " ", scores_label[ps_secondary]);
383 s = strcat(s, " ", scores_label[ps_primary]);
384 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
391 hud_fontsize = HUD_GetFontsize("hud_fontsize");
393 for(i = 1; i < argc - 1; ++i)
399 if(substring(str, 0, 1) == "?")
402 str = substring(str, 1, strlen(str) - 1);
405 slash = strstrofs(str, "/", 0);
408 pattern = substring(str, 0, slash);
409 str = substring(str, slash + 1, strlen(str) - (slash + 1));
411 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
415 strunzone(sbt_field_title[sbt_num_fields]);
416 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
417 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
418 str = strtolower(str);
422 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
423 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
424 case "kd": case "kdr": case "kdratio": case "k/d": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
425 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
426 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
427 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
428 case "dmg": sbt_field[sbt_num_fields] = SP_DMG; break;
429 case "dmgtaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
432 for(j = 0; j < MAX_SCORE; ++j)
433 if(str == strtolower(scores_label[j]))
434 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
442 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
446 sbt_field[sbt_num_fields] = j;
449 if(j == ps_secondary)
455 if(sbt_num_fields >= MAX_SBT_FIELDS)
459 if(scores_flags[ps_primary] & SFL_ALLOW_HIDE)
461 if(scores_flags[ps_secondary] & SFL_ALLOW_HIDE)
463 if(ps_primary == ps_secondary)
465 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
467 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
471 strunzone(sbt_field_title[sbt_num_fields]);
472 for(i = sbt_num_fields; i > 0; --i)
474 sbt_field_title[i] = sbt_field_title[i-1];
475 sbt_field_size[i] = sbt_field_size[i-1];
476 sbt_field[i] = sbt_field[i-1];
478 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
479 sbt_field[0] = SP_NAME;
481 LOG_INFO("fixed missing field 'name'\n");
485 strunzone(sbt_field_title[sbt_num_fields]);
486 for(i = sbt_num_fields; i > 1; --i)
488 sbt_field_title[i] = sbt_field_title[i-1];
489 sbt_field_size[i] = sbt_field_size[i-1];
490 sbt_field[i] = sbt_field[i-1];
492 sbt_field_title[1] = strzone("|");
493 sbt_field[1] = SP_SEPARATOR;
494 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
496 LOG_INFO("fixed missing field '|'\n");
499 else if(!have_separator)
501 strunzone(sbt_field_title[sbt_num_fields]);
502 sbt_field_title[sbt_num_fields] = strzone("|");
503 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
504 sbt_field[sbt_num_fields] = SP_SEPARATOR;
506 LOG_INFO("fixed missing field '|'\n");
510 strunzone(sbt_field_title[sbt_num_fields]);
511 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_secondary]));
512 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
513 sbt_field[sbt_num_fields] = ps_secondary;
515 LOG_INFOF("fixed missing field '%s'\n", scores_label[ps_secondary]);
519 strunzone(sbt_field_title[sbt_num_fields]);
520 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_primary]));
521 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
522 sbt_field[sbt_num_fields] = ps_primary;
524 LOG_INFOF("fixed missing field '%s'\n", scores_label[ps_primary]);
528 sbt_field[sbt_num_fields] = SP_END;
532 vector sbt_field_rgb;
533 string sbt_field_icon0;
534 string sbt_field_icon1;
535 string sbt_field_icon2;
536 vector sbt_field_icon0_rgb;
537 vector sbt_field_icon1_rgb;
538 vector sbt_field_icon2_rgb;
539 float sbt_field_icon0_alpha;
540 float sbt_field_icon1_alpha;
541 float sbt_field_icon2_alpha;
542 string Scoreboard_GetField(entity pl, int field)
545 float tmp, num, denom;
548 sbt_field_rgb = '1 1 1';
549 sbt_field_icon0 = "";
550 sbt_field_icon1 = "";
551 sbt_field_icon2 = "";
552 sbt_field_icon0_rgb = '1 1 1';
553 sbt_field_icon1_rgb = '1 1 1';
554 sbt_field_icon2_rgb = '1 1 1';
555 sbt_field_icon0_alpha = 1;
556 sbt_field_icon1_alpha = 1;
557 sbt_field_icon2_alpha = 1;
562 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
563 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
567 tmp = max(0, min(220, f-80)) / 220;
568 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
574 f = pl.ping_packetloss;
575 tmp = pl.ping_movementloss;
576 if(f == 0 && tmp == 0)
578 str = ftos(ceil(f * 100));
580 str = strcat(str, "~", ftos(ceil(tmp * 100)));
581 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
582 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
586 if(ready_waiting && pl.ready)
588 sbt_field_icon0 = "gfx/scoreboard/player_ready";
592 f = stof(getplayerkeyvalue(pl.sv_entnum, "colors"));
594 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
595 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
596 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
597 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
598 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
601 return entcs_GetName(pl.sv_entnum);
604 f = pl.(scores[SP_KILLS]);
605 f -= pl.(scores[SP_SUICIDES]);
609 num = pl.(scores[SP_KILLS]);
610 denom = pl.(scores[SP_DEATHS]);
613 sbt_field_rgb = '0 1 0';
614 str = sprintf("%d", num);
615 } else if(num <= 0) {
616 sbt_field_rgb = '1 0 0';
617 str = sprintf("%.1f", num/denom);
619 str = sprintf("%.1f", num/denom);
623 f = pl.(scores[SP_KILLS]);
624 f -= pl.(scores[SP_DEATHS]);
627 sbt_field_rgb = '0 1 0';
629 sbt_field_rgb = '1 1 1';
631 sbt_field_rgb = '1 0 0';
636 num = pl.(scores[SP_DMG]);
639 str = sprintf("%.1f k", num/denom);
643 num = pl.(scores[SP_DMGTAKEN]);
646 str = sprintf("%.1f k", num/denom);
650 tmp = pl.(scores[field]);
651 f = scores_flags[field];
652 if(field == ps_primary)
653 sbt_field_rgb = '1 1 0';
654 else if(field == ps_secondary)
655 sbt_field_rgb = '0 1 1';
657 sbt_field_rgb = '1 1 1';
658 return ScoreString(f, tmp);
663 float sbt_fixcolumnwidth_len;
664 float sbt_fixcolumnwidth_iconlen;
665 float sbt_fixcolumnwidth_marginlen;
667 string Scoreboard_FixColumnWidth(int i, string str)
672 field = sbt_field[i];
674 sbt_fixcolumnwidth_iconlen = 0;
676 if(sbt_field_icon0 != "")
678 sz = draw_getimagesize(sbt_field_icon0);
680 if(sbt_fixcolumnwidth_iconlen < f)
681 sbt_fixcolumnwidth_iconlen = f;
684 if(sbt_field_icon1 != "")
686 sz = draw_getimagesize(sbt_field_icon1);
688 if(sbt_fixcolumnwidth_iconlen < f)
689 sbt_fixcolumnwidth_iconlen = f;
692 if(sbt_field_icon2 != "")
694 sz = draw_getimagesize(sbt_field_icon2);
696 if(sbt_fixcolumnwidth_iconlen < f)
697 sbt_fixcolumnwidth_iconlen = f;
700 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
702 if(sbt_fixcolumnwidth_iconlen != 0)
703 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
705 sbt_fixcolumnwidth_marginlen = 0;
707 if(field == SP_NAME) // name gets all remaining space
711 namesize = panel_size.x;
712 for(j = 0; j < sbt_num_fields; ++j)
714 if (sbt_field[i] != SP_SEPARATOR)
715 namesize -= sbt_field_size[j] + hud_fontsize.x;
716 sbt_field_size[i] = namesize;
718 if (sbt_fixcolumnwidth_iconlen != 0)
719 namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
720 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
721 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
724 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
726 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
727 if(sbt_field_size[i] < f)
728 sbt_field_size[i] = f;
733 vector Scoreboard_DrawHeader(vector pos, vector rgb)
736 vector column_dim = eY * panel_size.y;
737 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
738 pos.x += hud_fontsize.x * 0.5;
739 for(i = 0; i < sbt_num_fields; ++i)
741 if(sbt_field[i] == SP_SEPARATOR)
743 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
746 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
747 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
748 pos.x += column_dim.x;
750 if(sbt_field[i] == SP_SEPARATOR)
752 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
753 for(i = sbt_num_fields - 1; i > 0; --i)
755 if(sbt_field[i] == SP_SEPARATOR)
758 pos.x -= sbt_field_size[i];
763 if (i == sbt_num_fields-1)
764 column_dim.x = sbt_field_size[i] + hud_fontsize.x * 0.5;
766 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
767 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
770 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
771 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
772 pos.x -= hud_fontsize.x;
777 pos.y += 1.25 * hud_fontsize.y;
781 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
783 TC(bool, is_self); TC(int, pl_number);
787 is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
788 if(is_spec && !is_self)
791 vector h_pos = item_pos;
792 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
793 // alternated rows highlighting
795 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
796 else if((sbt_highlight) && (!(pl_number % 2)))
797 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
799 vector pos = item_pos;
800 pos.x += hud_fontsize.x * 0.5;
801 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
802 vector tmp = '0 0 0';
804 for(i = 0; i < sbt_num_fields; ++i)
806 field = sbt_field[i];
807 if(field == SP_SEPARATOR)
810 if(is_spec && field != SP_NAME && field != SP_PING) {
811 pos.x += sbt_field_size[i] + hud_fontsize.x;
814 str = Scoreboard_GetField(pl, field);
815 str = Scoreboard_FixColumnWidth(i, str);
817 pos.x += sbt_field_size[i] + hud_fontsize.x;
819 if(field == SP_NAME) {
820 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
822 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
824 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
826 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
828 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
830 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
833 tmp.x = sbt_field_size[i] + hud_fontsize.x;
834 if(sbt_field_icon0 != "")
836 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);
838 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);
839 if(sbt_field_icon1 != "")
841 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);
843 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);
844 if(sbt_field_icon2 != "")
846 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);
848 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);
851 if(sbt_field[i] == SP_SEPARATOR)
853 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
854 for(i = sbt_num_fields-1; i > 0; --i)
856 field = sbt_field[i];
857 if(field == SP_SEPARATOR)
860 if(is_spec && field != SP_NAME && field != SP_PING) {
861 pos.x -= sbt_field_size[i] + hud_fontsize.x;
865 str = Scoreboard_GetField(pl, field);
866 str = Scoreboard_FixColumnWidth(i, str);
868 if(field == SP_NAME) {
869 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
871 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
873 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
875 tmp.x = sbt_fixcolumnwidth_len;
877 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
879 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
882 tmp.x = sbt_field_size[i];
883 if(sbt_field_icon0 != "")
885 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);
887 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);
888 if(sbt_field_icon1 != "")
890 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);
892 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);
893 if(sbt_field_icon2 != "")
895 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);
897 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);
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_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
911 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
912 panel_size.y += panel_bg_padding * 2;
913 HUD_Panel_DrawBg(scoreboard_fade_alpha);
915 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
919 panel_pos += '1 1 0' * panel_bg_padding;
920 panel_size -= '2 2 0' * panel_bg_padding;
924 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
928 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
930 pos.y += 1.25 * hud_fontsize.y;
933 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
935 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
938 // print header row and highlight columns
939 pos = Scoreboard_DrawHeader(panel_pos, rgb);
941 // fill the table and draw the rows
944 for(pl = players.sort_next; pl; pl = pl.sort_next)
946 if(pl.team != tm.team)
948 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
949 pos.y += 1.25 * hud_fontsize.y;
953 for(pl = players.sort_next; pl; pl = pl.sort_next)
955 if(pl.team == NUM_SPECTATOR)
957 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
958 pos.y += 1.25 * hud_fontsize.y;
962 panel_size.x += panel_bg_padding * 2; // restore initial width
966 float Scoreboard_WouldDraw() {
967 if (QuickMenu_IsOpened())
969 else if (HUD_Radar_Clickable())
971 else if (scoreboard_showscores)
973 else if (intermission == 1)
975 else if (intermission == 2)
977 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
979 else if (scoreboard_showscores_force)
984 float average_accuracy;
985 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
987 WepSet weapons_stat = WepSet_GetFromStat();
988 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
990 FOREACH(Weapons, it != WEP_Null, {
991 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
993 WepSet set = it.m_wepset;
994 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
998 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
999 if (weapon_cnt <= 0) return pos;
1002 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1004 int columnns = ceil(weapon_cnt / rows);
1008 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1009 pos.y += 1.25 * hud_fontsize.y;
1010 pos.y += panel_bg_border;
1013 panel_size.y = height * rows;
1014 panel_size.y += panel_bg_padding * 2;
1015 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1017 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1019 if(panel_bg_padding)
1021 panel_pos += '1 1 0' * panel_bg_padding;
1022 panel_size -= '2 2 0' * panel_bg_padding;
1026 vector tmp = panel_size;
1028 float fontsize = height * 1/3;
1029 float weapon_height = height * 2/3;
1030 float weapon_width = tmp.x / columnns / rows;
1033 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1037 // column highlighting
1038 for (int i = 0; i < columnns; ++i)
1040 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1043 for (int i = 0; i < rows; ++i)
1044 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1047 average_accuracy = 0;
1048 int weapons_with_stats = 0;
1050 pos.x += weapon_width / 2;
1052 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1055 Accuracy_LoadColors();
1057 float oldposx = pos.x;
1061 FOREACH(Weapons, it != WEP_Null, {
1062 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1064 WepSet set = it.m_wepset;
1065 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1069 if (weapon_stats >= 0)
1070 weapon_alpha = sbt_fg_alpha;
1072 weapon_alpha = 0.2 * sbt_fg_alpha;
1075 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1077 if (weapon_stats >= 0) {
1078 weapons_with_stats += 1;
1079 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1082 s = sprintf("%d%%", weapon_stats * 100);
1085 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1087 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1088 rgb = Accuracy_GetColor(weapon_stats);
1090 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1092 tmpos.x += weapon_width * rows;
1093 pos.x += weapon_width * rows;
1094 if (rows == 2 && column == columnns - 1) {
1102 if (weapons_with_stats)
1103 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1105 panel_size.x += panel_bg_padding * 2; // restore initial width
1109 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1111 pos.x += hud_fontsize.x * 0.25;
1112 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1113 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1114 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1116 pos.y += hud_fontsize.y;
1121 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1122 float stat_secrets_found, stat_secrets_total;
1123 float stat_monsters_killed, stat_monsters_total;
1127 // get monster stats
1128 stat_monsters_killed = STAT(MONSTERS_KILLED);
1129 stat_monsters_total = STAT(MONSTERS_TOTAL);
1130 stat_monsters_killed = 14;
1131 stat_monsters_total = 22;
1133 // get secrets stats
1134 stat_secrets_found = STAT(SECRETS_FOUND);
1135 stat_secrets_total = STAT(SECRETS_TOTAL);
1136 stat_secrets_found = 5;
1137 stat_secrets_total = 7;
1139 // get number of rows
1140 if(stat_secrets_total)
1142 if(stat_monsters_total)
1145 // if no rows, return
1149 // draw table header
1150 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1151 pos.y += 1.25 * hud_fontsize.y;
1152 pos.y += panel_bg_border;
1155 panel_size.y = hud_fontsize.y * rows;
1156 panel_size.y += panel_bg_padding * 2;
1157 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1159 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1161 if(panel_bg_padding)
1163 panel_pos += '1 1 0' * panel_bg_padding;
1164 panel_size -= '2 2 0' * panel_bg_padding;
1168 vector tmp = panel_size;
1171 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1174 if(stat_monsters_total)
1176 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1177 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1181 if(stat_secrets_total)
1183 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1184 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1187 panel_size.x += panel_bg_padding * 2; // restore initial width
1192 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1195 RANKINGS_RECEIVED_CNT = 0;
1196 for (i=RANKINGS_CNT-1; i>=0; --i)
1198 ++RANKINGS_RECEIVED_CNT;
1200 if (RANKINGS_RECEIVED_CNT == 0)
1203 vector hl_rgb = rgb + '0.5 0.5 0.5';
1205 pos.y += hud_fontsize.y;
1206 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1207 pos.y += 1.25 * hud_fontsize.y;
1208 pos.y += panel_bg_border;
1211 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1212 panel_size.y += panel_bg_padding * 2;
1213 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1215 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1217 if(panel_bg_padding)
1219 panel_pos += '1 1 0' * panel_bg_padding;
1220 panel_size -= '2 2 0' * panel_bg_padding;
1224 vector tmp = panel_size;
1227 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1230 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1237 n = grecordholder[i];
1238 p = count_ordinal(i+1);
1239 if(grecordholder[i] == entcs_GetName(player_localnum))
1240 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1241 else if(!(i % 2) && sbt_highlight)
1242 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1243 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1244 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);
1245 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1246 pos.y += 1.25 * hud_fontsize.y;
1249 panel_size.x += panel_bg_padding * 2; // restore initial width
1253 void Scoreboard_Draw()
1255 if(!autocvar__hud_configure)
1257 // frametime checks allow to toggle the scoreboard even when the game is paused
1258 if(scoreboard_active) {
1259 if(menu_enabled == 1)
1260 scoreboard_fade_alpha = 1;
1261 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1262 if (scoreboard_fadeinspeed && frametime)
1263 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1265 scoreboard_fade_alpha = 1;
1268 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1269 if (scoreboard_fadeoutspeed && frametime)
1270 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1272 scoreboard_fade_alpha = 0;
1275 if (!scoreboard_fade_alpha)
1279 scoreboard_fade_alpha = 0;
1281 if (autocvar_hud_panel_scoreboard_dynamichud)
1284 HUD_Scale_Disable();
1286 float hud_fade_alpha_save = hud_fade_alpha;
1287 if(menu_enabled == 1)
1290 hud_fade_alpha = scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1291 HUD_Panel_UpdateCvars();
1293 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1294 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1295 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1296 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1297 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1298 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1300 hud_fade_alpha = hud_fade_alpha_save;
1302 // don't overlap with con_notify
1303 if(!autocvar__hud_configure)
1304 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1306 Scoreboard_UpdatePlayerTeams();
1312 // Initializes position
1316 vector sb_heading_fontsize;
1317 sb_heading_fontsize = hud_fontsize * 2;
1318 draw_beginBoldFont();
1319 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1322 pos.y += sb_heading_fontsize.y;
1323 pos.y += panel_bg_border;
1325 // Draw the scoreboard
1326 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1329 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1333 vector panel_bg_color_save = panel_bg_color;
1334 vector team_score_baseoffset = eY * hud_fontsize.y - eX * (panel_bg_border + hud_fontsize.x * 0.5);
1335 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1337 if(tm.team == NUM_SPECTATOR)
1339 if(!tm.team && teamplay)
1342 draw_beginBoldFont();
1343 vector rgb = Team_ColorRGB(tm.team);
1344 str = ftos(tm.(teamscores[ts_primary]));
1345 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1347 if(ts_primary != ts_secondary)
1349 str = ftos(tm.(teamscores[ts_secondary]));
1350 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);
1353 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1354 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1355 else if(panel_bg_color_team > 0)
1356 panel_bg_color = rgb * panel_bg_color_team;
1358 panel_bg_color = rgb;
1359 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1361 panel_bg_color = panel_bg_color_save;
1365 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1367 if(tm.team == NUM_SPECTATOR)
1369 if(!tm.team && teamplay)
1372 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1376 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1377 if(race_speedaward) {
1378 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);
1379 pos.y += 1.25 * hud_fontsize.y;
1381 if(race_speedaward_alltimebest) {
1382 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);
1383 pos.y += 1.25 * hud_fontsize.y;
1385 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1387 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1388 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1390 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1395 for(pl = players.sort_next; pl; pl = pl.sort_next)
1397 if(pl.team != NUM_SPECTATOR)
1399 pos.y += 1.25 * hud_fontsize.y;
1400 Scoreboard_DrawItem(pos, panel_bg_color, pl, (pl.sv_entnum == player_localnum), specs);
1406 draw_beginBoldFont();
1407 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1409 pos.y += 1.25 * hud_fontsize.y;
1412 // Print info string
1414 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1415 tl = STAT(TIMELIMIT);
1416 fl = STAT(FRAGLIMIT);
1417 ll = STAT(LEADLIMIT);
1418 if(gametype == MAPINFO_TYPE_LMS)
1421 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1426 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1430 str = strcat(str, _(" or"));
1433 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], fl),
1434 (teamscores_label[ts_primary] == "score") ? CTX(_("SCO^points")) :
1435 (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1436 TranslateScoresLabel(teamscores_label[ts_primary])));
1440 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags[ps_primary], fl),
1441 (scores_label[ps_primary] == "score") ? CTX(_("SCO^points")) :
1442 (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1443 TranslateScoresLabel(scores_label[ps_primary])));
1448 if(tl > 0 || fl > 0)
1449 str = strcat(str, _(" or"));
1452 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], ll),
1453 (teamscores_label[ts_primary] == "score") ? CTX(_("SCO^points")) :
1454 (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1455 TranslateScoresLabel(teamscores_label[ts_primary])));
1459 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags[ps_primary], ll),
1460 (scores_label[ps_primary] == "score") ? CTX(_("SCO^points")) :
1461 (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1462 TranslateScoresLabel(scores_label[ps_primary])));
1467 pos.y += 1.2 * hud_fontsize.y;
1468 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1470 // print information about respawn status
1471 float respawn_time = STAT(RESPAWN_TIME);
1475 if(respawn_time < 0)
1477 // a negative number means we are awaiting respawn, time value is still the same
1478 respawn_time *= -1; // remove mark now that we checked it
1479 respawn_time = max(time, respawn_time); // don't show a negative value while the server is respawning the player (lag)
1481 str = sprintf(_("^1Respawning in ^3%s^1..."),
1482 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1483 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1485 count_seconds(respawn_time - time)
1489 else if(time < respawn_time)
1491 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1492 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1493 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1495 count_seconds(respawn_time - time)
1499 else if(time >= respawn_time)
1500 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1502 pos.y += 1.2 * hud_fontsize.y;
1503 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1506 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;