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 namesize += hud_fontsize.x;
717 sbt_field_size[i] = namesize;
719 if (sbt_fixcolumnwidth_iconlen != 0)
720 namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen;
721 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
722 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
725 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
727 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen;
728 if(sbt_field_size[i] < f)
729 sbt_field_size[i] = f;
734 vector Scoreboard_DrawHeader(vector pos, vector rgb)
737 vector column_dim = eY * panel_size.y;
738 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
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;
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.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
801 vector tmp = '0 0 0';
803 for(i = 0; i < sbt_num_fields; ++i)
805 field = sbt_field[i];
806 if(field == SP_SEPARATOR)
809 if(is_spec && field != SP_NAME && field != SP_PING) {
810 pos.x += sbt_field_size[i] + hud_fontsize.x;
813 str = Scoreboard_GetField(pl, field);
814 str = Scoreboard_FixColumnWidth(i, str);
816 pos.x += sbt_field_size[i] + hud_fontsize.x;
818 if(field == SP_NAME) {
819 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
821 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
823 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
825 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
827 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
829 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
832 tmp.x = sbt_field_size[i] + hud_fontsize.x;
833 if(sbt_field_icon0 != "")
835 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);
837 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);
838 if(sbt_field_icon1 != "")
840 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);
842 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);
843 if(sbt_field_icon2 != "")
845 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);
847 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);
850 if(sbt_field[i] == SP_SEPARATOR)
852 pos.x = item_pos.x + panel_size.x;
853 for(i = sbt_num_fields-1; i > 0; --i)
855 field = sbt_field[i];
856 if(field == SP_SEPARATOR)
859 if(is_spec && field != SP_NAME && field != SP_PING) {
860 pos.x -= sbt_field_size[i] + hud_fontsize.x;
864 str = Scoreboard_GetField(pl, field);
865 str = Scoreboard_FixColumnWidth(i, str);
867 if(field == SP_NAME) {
868 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
870 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
872 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
874 tmp.x = sbt_fixcolumnwidth_len;
876 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
878 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
881 tmp.x = sbt_field_size[i];
882 if(sbt_field_icon0 != "")
884 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);
886 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);
887 if(sbt_field_icon1 != "")
889 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);
891 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);
892 if(sbt_field_icon2 != "")
894 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);
896 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);
897 pos.x -= sbt_field_size[i] + hud_fontsize.x;
902 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
905 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
910 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
911 panel_size.y += panel_bg_padding * 2;
912 HUD_Panel_DrawBg(scoreboard_fade_alpha);
914 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
918 panel_pos += '1 1 0' * panel_bg_padding;
919 panel_size -= '2 2 0' * panel_bg_padding;
923 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
927 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
929 pos.y += 1.25 * hud_fontsize.y;
932 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
934 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
937 // print header row and highlight columns
938 pos = Scoreboard_DrawHeader(panel_pos, rgb);
940 // fill the table and draw the rows
943 for(pl = players.sort_next; pl; pl = pl.sort_next)
945 if(pl.team != tm.team)
947 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
948 pos.y += 1.25 * hud_fontsize.y;
952 for(pl = players.sort_next; pl; pl = pl.sort_next)
954 if(pl.team == NUM_SPECTATOR)
956 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
957 pos.y += 1.25 * hud_fontsize.y;
961 panel_size.x += panel_bg_padding * 2; // restore initial width
965 float Scoreboard_WouldDraw() {
966 if (QuickMenu_IsOpened())
968 else if (HUD_Radar_Clickable())
970 else if (scoreboard_showscores)
972 else if (intermission == 1)
974 else if (intermission == 2)
976 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
978 else if (scoreboard_showscores_force)
983 float average_accuracy;
984 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
986 WepSet weapons_stat = WepSet_GetFromStat();
987 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
989 FOREACH(Weapons, it != WEP_Null, {
990 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
992 WepSet set = it.m_wepset;
993 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
997 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
998 if (weapon_cnt <= 0) return pos;
1001 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1003 int columnns = ceil(weapon_cnt / rows);
1007 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1008 pos.y += 1.25 * hud_fontsize.y;
1009 pos.y += panel_bg_border;
1012 panel_size.y = height * rows;
1013 panel_size.y += panel_bg_padding * 2;
1014 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1016 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
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 fontsize = height * 1/3;
1028 float weapon_height = height * 2/3;
1029 float weapon_width = tmp.x / columnns / rows;
1032 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1036 // column highlighting
1037 for (int i = 0; i < columnns; ++i)
1039 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1042 for (int i = 0; i < rows; ++i)
1043 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1046 average_accuracy = 0;
1047 int weapons_with_stats = 0;
1049 pos.x += weapon_width / 2;
1051 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1054 Accuracy_LoadColors();
1056 float oldposx = pos.x;
1060 FOREACH(Weapons, it != WEP_Null, {
1061 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1063 WepSet set = it.m_wepset;
1064 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1068 if (weapon_stats >= 0)
1069 weapon_alpha = sbt_fg_alpha;
1071 weapon_alpha = 0.2 * sbt_fg_alpha;
1074 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1076 if (weapon_stats >= 0) {
1077 weapons_with_stats += 1;
1078 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1081 s = sprintf("%d%%", weapon_stats * 100);
1084 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1086 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1087 rgb = Accuracy_GetColor(weapon_stats);
1089 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1091 tmpos.x += weapon_width * rows;
1092 pos.x += weapon_width * rows;
1093 if (rows == 2 && column == columnns - 1) {
1101 if (weapons_with_stats)
1102 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1104 panel_size.x += panel_bg_padding * 2; // restore initial width
1108 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1110 pos.x += hud_fontsize.x * 0.25;
1111 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1112 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1113 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1115 pos.y += hud_fontsize.y;
1120 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1121 float stat_secrets_found, stat_secrets_total;
1122 float stat_monsters_killed, stat_monsters_total;
1126 // get monster stats
1127 stat_monsters_killed = STAT(MONSTERS_KILLED);
1128 stat_monsters_total = STAT(MONSTERS_TOTAL);
1129 stat_monsters_killed = 14;
1130 stat_monsters_total = 22;
1132 // get secrets stats
1133 stat_secrets_found = STAT(SECRETS_FOUND);
1134 stat_secrets_total = STAT(SECRETS_TOTAL);
1135 stat_secrets_found = 5;
1136 stat_secrets_total = 7;
1138 // get number of rows
1139 if(stat_secrets_total)
1141 if(stat_monsters_total)
1144 // if no rows, return
1148 // draw table header
1149 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1150 pos.y += 1.25 * hud_fontsize.y;
1151 pos.y += panel_bg_border;
1154 panel_size.y = hud_fontsize.y * rows;
1155 panel_size.y += panel_bg_padding * 2;
1156 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1158 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1160 if(panel_bg_padding)
1162 panel_pos += '1 1 0' * panel_bg_padding;
1163 panel_size -= '2 2 0' * panel_bg_padding;
1167 vector tmp = panel_size;
1170 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1173 if(stat_monsters_total)
1175 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1176 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1180 if(stat_secrets_total)
1182 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1183 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1186 panel_size.x += panel_bg_padding * 2; // restore initial width
1191 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1194 RANKINGS_RECEIVED_CNT = 0;
1195 for (i=RANKINGS_CNT-1; i>=0; --i)
1197 ++RANKINGS_RECEIVED_CNT;
1199 if (RANKINGS_RECEIVED_CNT == 0)
1202 vector hl_rgb = rgb + '0.5 0.5 0.5';
1204 pos.y += hud_fontsize.y;
1205 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1206 pos.y += 1.25 * hud_fontsize.y;
1207 pos.y += panel_bg_border;
1210 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1211 panel_size.y += panel_bg_padding * 2;
1212 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1214 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
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 // frametime checks allow to toggle the scoreboard even when the game is paused
1257 if(scoreboard_active) {
1258 if(menu_enabled == 1)
1259 scoreboard_fade_alpha = 1;
1260 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1261 if (scoreboard_fadeinspeed && frametime)
1262 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1264 scoreboard_fade_alpha = 1;
1267 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1268 if (scoreboard_fadeoutspeed && frametime)
1269 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1271 scoreboard_fade_alpha = 0;
1274 if (!scoreboard_fade_alpha)
1278 scoreboard_fade_alpha = 0;
1280 if (autocvar_hud_panel_scoreboard_dynamichud)
1283 HUD_Scale_Disable();
1285 float hud_fade_alpha_save = hud_fade_alpha;
1286 if(menu_enabled == 1)
1289 hud_fade_alpha = scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1290 HUD_Panel_UpdateCvars();
1292 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1293 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1294 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1295 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1296 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1297 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1299 hud_fade_alpha = hud_fade_alpha_save;
1301 // don't overlap with con_notify
1302 if(!autocvar__hud_configure)
1303 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1305 Scoreboard_UpdatePlayerTeams();
1311 // Initializes position
1315 vector sb_heading_fontsize;
1316 sb_heading_fontsize = hud_fontsize * 2;
1317 draw_beginBoldFont();
1318 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1321 pos.y += sb_heading_fontsize.y;
1322 pos.y += panel_bg_border;
1324 // Draw the scoreboard
1325 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1328 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1332 vector panel_bg_color_save = panel_bg_color;
1333 vector team_score_baseoffset = eY * hud_fontsize.y - eX * (panel_bg_border + hud_fontsize.x * 0.5);
1334 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1336 if(tm.team == NUM_SPECTATOR)
1338 if(!tm.team && teamplay)
1341 draw_beginBoldFont();
1342 vector rgb = Team_ColorRGB(tm.team);
1343 str = ftos(tm.(teamscores[ts_primary]));
1344 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1346 if(ts_primary != ts_secondary)
1348 str = ftos(tm.(teamscores[ts_secondary]));
1349 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);
1352 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1353 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1354 else if(panel_bg_color_team > 0)
1355 panel_bg_color = rgb * panel_bg_color_team;
1357 panel_bg_color = rgb;
1358 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1360 panel_bg_color = panel_bg_color_save;
1364 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1366 if(tm.team == NUM_SPECTATOR)
1368 if(!tm.team && teamplay)
1371 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1375 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1376 if(race_speedaward) {
1377 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);
1378 pos.y += 1.25 * hud_fontsize.y;
1380 if(race_speedaward_alltimebest) {
1381 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);
1382 pos.y += 1.25 * hud_fontsize.y;
1384 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1386 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1387 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1389 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1394 for(pl = players.sort_next; pl; pl = pl.sort_next)
1396 if(pl.team != NUM_SPECTATOR)
1398 pos.y += 1.25 * hud_fontsize.y;
1399 Scoreboard_DrawItem(pos, panel_bg_color, pl, (pl.sv_entnum == player_localnum), specs);
1405 draw_beginBoldFont();
1406 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1408 pos.y += 1.25 * hud_fontsize.y;
1411 // Print info string
1413 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1414 tl = STAT(TIMELIMIT);
1415 fl = STAT(FRAGLIMIT);
1416 ll = STAT(LEADLIMIT);
1417 if(gametype == MAPINFO_TYPE_LMS)
1420 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1425 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1429 str = strcat(str, _(" or"));
1432 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], fl),
1433 (teamscores_label[ts_primary] == "score") ? CTX(_("SCO^points")) :
1434 (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1435 TranslateScoresLabel(teamscores_label[ts_primary])));
1439 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags[ps_primary], fl),
1440 (scores_label[ps_primary] == "score") ? CTX(_("SCO^points")) :
1441 (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1442 TranslateScoresLabel(scores_label[ps_primary])));
1447 if(tl > 0 || fl > 0)
1448 str = strcat(str, _(" or"));
1451 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], ll),
1452 (teamscores_label[ts_primary] == "score") ? CTX(_("SCO^points")) :
1453 (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1454 TranslateScoresLabel(teamscores_label[ts_primary])));
1458 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags[ps_primary], ll),
1459 (scores_label[ps_primary] == "score") ? CTX(_("SCO^points")) :
1460 (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1461 TranslateScoresLabel(scores_label[ps_primary])));
1466 pos.y += 1.2 * hud_fontsize.y;
1467 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1469 // print information about respawn status
1470 float respawn_time = STAT(RESPAWN_TIME);
1474 if(respawn_time < 0)
1476 // a negative number means we are awaiting respawn, time value is still the same
1477 respawn_time *= -1; // remove mark now that we checked it
1478 respawn_time = max(time, respawn_time); // don't show a negative value while the server is respawning the player (lag)
1480 str = sprintf(_("^1Respawning in ^3%s^1..."),
1481 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1482 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1484 count_seconds(respawn_time - time)
1488 else if(time < respawn_time)
1490 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1491 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1492 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1494 count_seconds(respawn_time - time)
1498 else if(time >= respawn_time)
1499 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1501 pos.y += 1.2 * hud_fontsize.y;
1502 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1505 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;