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;
29 bool autocvar_hud_panel_scoreboard_accuracy = true;
30 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
31 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
33 bool autocvar_hud_panel_scoreboard_dynamichud = false;
36 void drawstringright(vector, string, vector, vector, float, float);
37 void drawstringcenter(vector, string, vector, vector, float, float);
39 // wrapper to put all possible scores titles through gettext
40 string TranslateScoresLabel(string l)
44 case "bckills": return CTX(_("SCO^bckills"));
45 case "bctime": return CTX(_("SCO^bctime"));
46 case "caps": return CTX(_("SCO^caps"));
47 case "captime": return CTX(_("SCO^captime"));
48 case "deaths": return CTX(_("SCO^deaths"));
49 case "destroyed": return CTX(_("SCO^destroyed"));
50 case "dmg": return CTX(_("SCO^dmg"));
51 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
52 case "drops": return CTX(_("SCO^drops"));
53 case "faults": return CTX(_("SCO^faults"));
54 case "fckills": return CTX(_("SCO^fckills"));
55 case "goals": return CTX(_("SCO^goals"));
56 case "kckills": return CTX(_("SCO^kckills"));
57 case "kdratio": return CTX(_("SCO^kdratio"));
58 case "k/d": return CTX(_("SCO^k/d"));
59 case "kd": return CTX(_("SCO^kd"));
60 case "kdr": return CTX(_("SCO^kdr"));
61 case "kills": return CTX(_("SCO^kills"));
62 case "laps": return CTX(_("SCO^laps"));
63 case "lives": return CTX(_("SCO^lives"));
64 case "losses": return CTX(_("SCO^losses"));
65 case "name": return CTX(_("SCO^name"));
66 case "sum": return CTX(_("SCO^sum"));
67 case "nick": return CTX(_("SCO^nick"));
68 case "objectives": return CTX(_("SCO^objectives"));
69 case "pickups": return CTX(_("SCO^pickups"));
70 case "ping": return CTX(_("SCO^ping"));
71 case "pl": return CTX(_("SCO^pl"));
72 case "pushes": return CTX(_("SCO^pushes"));
73 case "rank": return CTX(_("SCO^rank"));
74 case "returns": return CTX(_("SCO^returns"));
75 case "revivals": return CTX(_("SCO^revivals"));
76 case "score": return CTX(_("SCO^score"));
77 case "suicides": return CTX(_("SCO^suicides"));
78 case "takes": return CTX(_("SCO^takes"));
79 case "ticks": return CTX(_("SCO^ticks"));
84 void Scoreboard_InitScores()
88 ps_primary = ps_secondary = ts_primary = ts_secondary = -1;
89 for(i = 0; i < MAX_SCORE; ++i)
91 f = (scores_flags[i] & SFL_SORT_PRIO_MASK);
92 if(f == SFL_SORT_PRIO_PRIMARY)
94 if(f == SFL_SORT_PRIO_SECONDARY)
97 if(ps_secondary == -1)
98 ps_secondary = ps_primary;
100 for(i = 0; i < MAX_TEAMSCORE; ++i)
102 f = (teamscores_flags[i] & SFL_SORT_PRIO_MASK);
103 if(f == SFL_SORT_PRIO_PRIMARY)
105 if(f == SFL_SORT_PRIO_SECONDARY)
108 if(ts_secondary == -1)
109 ts_secondary = ts_primary;
111 Cmd_Scoreboard_SetFields(0);
114 float SetTeam(entity pl, float Team);
116 void Scoreboard_UpdatePlayerTeams()
123 for(pl = players.sort_next; pl; pl = pl.sort_next)
126 Team = entcs_GetScoreTeam(pl.sv_entnum);
127 if(SetTeam(pl, Team))
130 Scoreboard_UpdatePlayerPos(pl);
134 pl = players.sort_next;
139 print(strcat("PNUM: ", ftos(num), "\n"));
144 int Scoreboard_CompareScore(int vl, int vr, int f)
146 TC(int, vl); TC(int, vr); TC(int, f);
147 if(f & SFL_ZERO_IS_WORST)
149 if(vl == 0 && vr != 0)
151 if(vl != 0 && vr == 0)
155 return IS_INCREASING(f);
157 return IS_DECREASING(f);
161 float Scoreboard_ComparePlayerScores(entity left, entity right)
164 vl = entcs_GetTeam(left.sv_entnum);
165 vr = entcs_GetTeam(right.sv_entnum);
177 if(vl == NUM_SPECTATOR)
179 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
181 if(!left.gotscores && right.gotscores)
186 r = Scoreboard_CompareScore(left.scores[ps_primary], right.scores[ps_primary], scores_flags[ps_primary]);
190 r = Scoreboard_CompareScore(left.scores[ps_secondary], right.scores[ps_secondary], scores_flags[ps_secondary]);
195 for(i = 0; i < MAX_SCORE; ++i)
197 r = Scoreboard_CompareScore(left.scores[i], right.scores[i], scores_flags[i]);
202 if (left.sv_entnum < right.sv_entnum)
208 void Scoreboard_UpdatePlayerPos(entity player)
211 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
213 SORT_SWAP(player, ent);
215 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
217 SORT_SWAP(ent, player);
221 float Scoreboard_CompareTeamScores(entity left, entity right)
225 if(left.team == NUM_SPECTATOR)
227 if(right.team == NUM_SPECTATOR)
230 r = Scoreboard_CompareScore(left.teamscores[ts_primary], right.teamscores[ts_primary], teamscores_flags[ts_primary]);
234 r = Scoreboard_CompareScore(left.teamscores[ts_secondary], right.teamscores[ts_secondary], teamscores_flags[ts_secondary]);
238 for(i = 0; i < MAX_SCORE; ++i)
240 r = Scoreboard_CompareScore(left.teamscores[i], right.teamscores[i], teamscores_flags[i]);
245 if (left.team < right.team)
251 void Scoreboard_UpdateTeamPos(entity Team)
254 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
256 SORT_SWAP(Team, ent);
258 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
260 SORT_SWAP(ent, Team);
264 void Cmd_Scoreboard_Help()
266 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
267 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
268 LOG_INFO(_("Usage:\n"));
269 LOG_INFO(_("^2scoreboard_columns_set default\n"));
270 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
271 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
272 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n\n"));
274 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
275 LOG_INFO(_("^3ping^7 Ping time\n"));
276 LOG_INFO(_("^3pl^7 Packet loss\n"));
277 LOG_INFO(_("^3kills^7 Number of kills\n"));
278 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
279 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
280 LOG_INFO(_("^3frags^7 kills - suicides\n"));
281 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
282 LOG_INFO(_("^3dmg^7 The total damage done\n"));
283 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
284 LOG_INFO(_("^3sum^7 frags - deaths\n"));
285 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
286 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
287 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
288 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
289 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
290 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
291 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
292 LOG_INFO(_("^3rank^7 Player rank\n"));
293 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
294 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
295 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
296 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
297 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
298 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
299 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
300 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
301 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
302 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
303 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
304 LOG_INFO(_("^3score^7 Total score\n\n"));
306 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
307 "of game types, then a slash, to make the field show up only in these\n"
308 "or in all but these game types. You can also specify 'all' as a\n"
309 "field to show all fields available for the current game mode.\n\n"));
311 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
312 "include/exclude ALL teams/noteams game modes.\n\n"));
314 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
315 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
316 "right of the vertical bar aligned to the right.\n"));
317 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
318 "other gamemodes except DM.\n"));
321 // NOTE: adding a gametype with ? to not warn for an optional field
322 // make sure it's excluded in a previous exclusive rule, if any
323 // otherwise the previous exclusive rule warns anyway
324 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
325 #define SCOREBOARD_DEFAULT_COLUMNS \
327 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
328 " -teams,lms/deaths +ft,tdm/deaths" \
329 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
330 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
331 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
332 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
333 " +lms/lives +lms/rank" \
334 " +kh/caps +kh/pushes +kh/destroyed" \
335 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
336 " +as/objectives +nb/faults +nb/goals" \
337 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
338 " -lms,rc,cts,inv,nb/score"
340 void Cmd_Scoreboard_SetFields(int argc)
345 float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
350 // set up a temporary scoreboard layout
351 // no layout can be properly set up until score_info data haven't been received
352 argc = tokenizebyseparator("0 1 ping pl name | score", " ");
354 scores_label[ps_primary] = strzone("score");
355 scores_flags[ps_primary] = SFL_ALLOW_HIDE;
358 // TODO: re enable with gametype dependant cvars?
359 if(argc < 3) // no arguments provided
360 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
363 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
367 if(argv(2) == "default")
368 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
369 else if(argv(2) == "all")
372 s = "ping pl name |";
373 for(i = 0; i < MAX_SCORE; ++i)
376 if(i != ps_secondary)
377 if(scores_label[i] != "")
378 s = strcat(s, " ", scores_label[i]);
380 if(ps_secondary != ps_primary)
381 s = strcat(s, " ", scores_label[ps_secondary]);
382 s = strcat(s, " ", scores_label[ps_primary]);
383 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
390 hud_fontsize = HUD_GetFontsize("hud_fontsize");
392 for(i = 1; i < argc - 1; ++i)
398 if(substring(str, 0, 1) == "?")
401 str = substring(str, 1, strlen(str) - 1);
404 slash = strstrofs(str, "/", 0);
407 pattern = substring(str, 0, slash);
408 str = substring(str, slash + 1, strlen(str) - (slash + 1));
410 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
414 strunzone(sbt_field_title[sbt_num_fields]);
415 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
416 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
417 str = strtolower(str);
421 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
422 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
423 case "kd": case "kdr": case "kdratio": case "k/d": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
424 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
425 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
426 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
427 case "dmg": sbt_field[sbt_num_fields] = SP_DMG; break;
428 case "dmgtaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
431 for(j = 0; j < MAX_SCORE; ++j)
432 if(str == strtolower(scores_label[j]))
433 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
441 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
445 sbt_field[sbt_num_fields] = j;
448 if(j == ps_secondary)
454 if(sbt_num_fields >= MAX_SBT_FIELDS)
458 if(scores_flags[ps_primary] & SFL_ALLOW_HIDE)
460 if(scores_flags[ps_secondary] & SFL_ALLOW_HIDE)
462 if(ps_primary == ps_secondary)
464 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
466 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
470 strunzone(sbt_field_title[sbt_num_fields]);
471 for(i = sbt_num_fields; i > 0; --i)
473 sbt_field_title[i] = sbt_field_title[i-1];
474 sbt_field_size[i] = sbt_field_size[i-1];
475 sbt_field[i] = sbt_field[i-1];
477 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
478 sbt_field[0] = SP_NAME;
480 LOG_INFO("fixed missing field 'name'\n");
484 strunzone(sbt_field_title[sbt_num_fields]);
485 for(i = sbt_num_fields; i > 1; --i)
487 sbt_field_title[i] = sbt_field_title[i-1];
488 sbt_field_size[i] = sbt_field_size[i-1];
489 sbt_field[i] = sbt_field[i-1];
491 sbt_field_title[1] = strzone("|");
492 sbt_field[1] = SP_SEPARATOR;
493 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
495 LOG_INFO("fixed missing field '|'\n");
498 else if(!have_separator)
500 strunzone(sbt_field_title[sbt_num_fields]);
501 sbt_field_title[sbt_num_fields] = strzone("|");
502 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
503 sbt_field[sbt_num_fields] = SP_SEPARATOR;
505 LOG_INFO("fixed missing field '|'\n");
509 strunzone(sbt_field_title[sbt_num_fields]);
510 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_secondary]));
511 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
512 sbt_field[sbt_num_fields] = ps_secondary;
514 LOG_INFOF("fixed missing field '%s'\n", scores_label[ps_secondary]);
518 strunzone(sbt_field_title[sbt_num_fields]);
519 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_primary]));
520 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
521 sbt_field[sbt_num_fields] = ps_primary;
523 LOG_INFOF("fixed missing field '%s'\n", scores_label[ps_primary]);
527 sbt_field[sbt_num_fields] = SP_END;
531 vector sbt_field_rgb;
532 string sbt_field_icon0;
533 string sbt_field_icon1;
534 string sbt_field_icon2;
535 vector sbt_field_icon0_rgb;
536 vector sbt_field_icon1_rgb;
537 vector sbt_field_icon2_rgb;
538 float sbt_field_icon0_alpha;
539 float sbt_field_icon1_alpha;
540 float sbt_field_icon2_alpha;
541 string Scoreboard_GetField(entity pl, int field)
544 float tmp, num, denom;
547 sbt_field_rgb = '1 1 1';
548 sbt_field_icon0 = "";
549 sbt_field_icon1 = "";
550 sbt_field_icon2 = "";
551 sbt_field_icon0_rgb = '1 1 1';
552 sbt_field_icon1_rgb = '1 1 1';
553 sbt_field_icon2_rgb = '1 1 1';
554 sbt_field_icon0_alpha = 1;
555 sbt_field_icon1_alpha = 1;
556 sbt_field_icon2_alpha = 1;
561 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
562 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
566 tmp = max(0, min(220, f-80)) / 220;
567 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
573 f = pl.ping_packetloss;
574 tmp = pl.ping_movementloss;
575 if(f == 0 && tmp == 0)
577 str = ftos(ceil(f * 100));
579 str = strcat(str, "~", ftos(ceil(tmp * 100)));
580 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
581 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
585 if(ready_waiting && pl.ready)
587 sbt_field_icon0 = "gfx/scoreboard/player_ready";
591 f = stof(getplayerkeyvalue(pl.sv_entnum, "colors"));
593 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
594 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
595 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
596 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
597 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
600 return entcs_GetName(pl.sv_entnum);
603 f = pl.(scores[SP_KILLS]);
604 f -= pl.(scores[SP_SUICIDES]);
608 num = pl.(scores[SP_KILLS]);
609 denom = pl.(scores[SP_DEATHS]);
612 sbt_field_rgb = '0 1 0';
613 str = sprintf("%d", num);
614 } else if(num <= 0) {
615 sbt_field_rgb = '1 0 0';
616 str = sprintf("%.1f", num/denom);
618 str = sprintf("%.1f", num/denom);
622 f = pl.(scores[SP_KILLS]);
623 f -= pl.(scores[SP_DEATHS]);
626 sbt_field_rgb = '0 1 0';
628 sbt_field_rgb = '1 1 1';
630 sbt_field_rgb = '1 0 0';
635 num = pl.(scores[SP_DMG]);
638 str = sprintf("%.1f k", num/denom);
642 num = pl.(scores[SP_DMGTAKEN]);
645 str = sprintf("%.1f k", num/denom);
649 tmp = pl.(scores[field]);
650 f = scores_flags[field];
651 if(field == ps_primary)
652 sbt_field_rgb = '1 1 0';
653 else if(field == ps_secondary)
654 sbt_field_rgb = '0 1 1';
656 sbt_field_rgb = '1 1 1';
657 return ScoreString(f, tmp);
662 float sbt_fixcolumnwidth_len;
663 float sbt_fixcolumnwidth_iconlen;
664 float sbt_fixcolumnwidth_marginlen;
666 string Scoreboard_FixColumnWidth(int i, string str)
671 field = sbt_field[i];
673 sbt_fixcolumnwidth_iconlen = 0;
675 if(sbt_field_icon0 != "")
677 sz = draw_getimagesize(sbt_field_icon0);
679 if(sbt_fixcolumnwidth_iconlen < f)
680 sbt_fixcolumnwidth_iconlen = f;
683 if(sbt_field_icon1 != "")
685 sz = draw_getimagesize(sbt_field_icon1);
687 if(sbt_fixcolumnwidth_iconlen < f)
688 sbt_fixcolumnwidth_iconlen = f;
691 if(sbt_field_icon2 != "")
693 sz = draw_getimagesize(sbt_field_icon2);
695 if(sbt_fixcolumnwidth_iconlen < f)
696 sbt_fixcolumnwidth_iconlen = f;
699 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
701 if(sbt_fixcolumnwidth_iconlen != 0)
702 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
704 sbt_fixcolumnwidth_marginlen = 0;
706 if(field == SP_NAME) // name gets all remaining space
710 namesize = panel_size.x;
711 for(j = 0; j < sbt_num_fields; ++j)
713 if (sbt_field[i] != SP_SEPARATOR)
714 namesize -= sbt_field_size[j] + hud_fontsize.x;
715 namesize += hud_fontsize.x;
716 sbt_field_size[i] = namesize;
718 if (sbt_fixcolumnwidth_iconlen != 0)
719 namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen;
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;
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 for(i = 0; i < sbt_num_fields; ++i)
740 if(sbt_field[i] == SP_SEPARATOR)
742 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
745 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
746 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
747 pos.x += column_dim.x;
749 if(sbt_field[i] == SP_SEPARATOR)
751 pos.x = panel_pos.x + panel_size.x;
752 for(i = sbt_num_fields - 1; i > 0; --i)
754 if(sbt_field[i] == SP_SEPARATOR)
757 pos.x -= sbt_field_size[i];
762 if (i == sbt_num_fields-1)
763 column_dim.x = sbt_field_size[i] + hud_fontsize.x * 0.5;
765 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);
769 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
770 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
771 pos.x -= hud_fontsize.x;
776 pos.y += 1.25 * hud_fontsize.y;
780 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
782 TC(bool, is_self); TC(int, pl_number);
786 is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
787 if(is_spec && !is_self)
790 vector h_pos = item_pos;
791 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
792 // alternated rows highlighting
794 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
795 else if((sbt_highlight) && (!(pl_number % 2)))
796 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
798 vector pos = item_pos;
799 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
800 vector tmp = '0 0 0';
802 for(i = 0; i < sbt_num_fields; ++i)
804 field = sbt_field[i];
805 if(field == SP_SEPARATOR)
808 if(is_spec && field != SP_NAME && field != SP_PING) {
809 pos.x += sbt_field_size[i] + hud_fontsize.x;
812 str = Scoreboard_GetField(pl, field);
813 str = Scoreboard_FixColumnWidth(i, str);
815 pos.x += sbt_field_size[i] + hud_fontsize.x;
817 if(field == SP_NAME) {
818 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
820 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
822 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
824 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
826 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
828 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
831 tmp.x = sbt_field_size[i] + hud_fontsize.x;
832 if(sbt_field_icon0 != "")
834 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);
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, DRAWFLAG_NORMAL);
837 if(sbt_field_icon1 != "")
839 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);
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, DRAWFLAG_NORMAL);
842 if(sbt_field_icon2 != "")
844 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);
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, DRAWFLAG_NORMAL);
849 if(sbt_field[i] == SP_SEPARATOR)
851 pos.x = item_pos.x + panel_size.x;
852 for(i = sbt_num_fields-1; i > 0; --i)
854 field = sbt_field[i];
855 if(field == SP_SEPARATOR)
858 if(is_spec && field != SP_NAME && field != SP_PING) {
859 pos.x -= sbt_field_size[i] + hud_fontsize.x;
863 str = Scoreboard_GetField(pl, field);
864 str = Scoreboard_FixColumnWidth(i, str);
866 if(field == SP_NAME) {
867 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
869 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
871 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
873 tmp.x = sbt_fixcolumnwidth_len;
875 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
877 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
880 tmp.x = sbt_field_size[i];
881 if(sbt_field_icon0 != "")
883 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);
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, DRAWFLAG_NORMAL);
886 if(sbt_field_icon1 != "")
888 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);
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, DRAWFLAG_NORMAL);
891 if(sbt_field_icon2 != "")
893 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);
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, DRAWFLAG_NORMAL);
896 pos.x -= sbt_field_size[i] + hud_fontsize.x;
901 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
904 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
909 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
910 panel_size.y += panel_bg_padding * 2;
911 HUD_Panel_DrawBg(scoreboard_fade_alpha);
913 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
917 panel_pos += '1 1 0' * panel_bg_padding;
918 panel_size -= '2 2 0' * panel_bg_padding;
922 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
926 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
928 pos.y += 1.25 * hud_fontsize.y;
931 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
933 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
936 // print header row and highlight columns
937 pos = Scoreboard_DrawHeader(panel_pos, rgb);
939 // fill the table and draw the rows
942 for(pl = players.sort_next; pl; pl = pl.sort_next)
944 if(pl.team != tm.team)
946 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
947 pos.y += 1.25 * hud_fontsize.y;
951 for(pl = players.sort_next; pl; pl = pl.sort_next)
953 if(pl.team == NUM_SPECTATOR)
955 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
956 pos.y += 1.25 * hud_fontsize.y;
960 panel_size.x += panel_bg_padding * 2; // restore initial width
964 float Scoreboard_WouldDraw() {
965 if (QuickMenu_IsOpened())
967 else if (HUD_Radar_Clickable())
969 else if (scoreboard_showscores)
971 else if (intermission == 1)
973 else if (intermission == 2)
975 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
977 else if (scoreboard_showscores_force)
982 float average_accuracy;
983 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
985 WepSet weapons_stat = WepSet_GetFromStat();
986 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
988 FOREACH(Weapons, it != WEP_Null, {
989 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
991 WepSet set = it.m_wepset;
992 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
996 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
997 if (weapon_cnt <= 0) return pos;
1000 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1002 int columnns = ceil(weapon_cnt / rows);
1006 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1007 pos.y += 1.25 * hud_fontsize.y;
1008 pos.y += panel_bg_border;
1011 panel_size.y = height * rows;
1012 panel_size.y += panel_bg_padding * 2;
1013 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1015 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1017 if(panel_bg_padding)
1019 panel_pos += '1 1 0' * panel_bg_padding;
1020 panel_size -= '2 2 0' * panel_bg_padding;
1024 vector tmp = panel_size;
1026 float fontsize = height * 1/3;
1027 float weapon_height = height * 2/3;
1028 float weapon_width = tmp.x / columnns / rows;
1031 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1035 // column highlighting
1036 for (int i = 0; i < columnns; ++i)
1038 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1041 for (int i = 0; i < rows; ++i)
1042 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1045 average_accuracy = 0;
1046 int weapons_with_stats = 0;
1048 pos.x += weapon_width / 2;
1050 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1053 Accuracy_LoadColors();
1055 float oldposx = pos.x;
1059 FOREACH(Weapons, it != WEP_Null, {
1060 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1062 WepSet set = it.m_wepset;
1063 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1067 if (weapon_stats >= 0)
1068 weapon_alpha = sbt_fg_alpha;
1070 weapon_alpha = 0.2 * sbt_fg_alpha;
1073 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1075 if (weapon_stats >= 0) {
1076 weapons_with_stats += 1;
1077 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1080 s = sprintf("%d%%", weapon_stats * 100);
1083 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1085 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1086 rgb = Accuracy_GetColor(weapon_stats);
1088 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1090 tmpos.x += weapon_width * rows;
1091 pos.x += weapon_width * rows;
1092 if (rows == 2 && column == columnns - 1) {
1100 if (weapons_with_stats)
1101 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1103 panel_size.x += panel_bg_padding * 2; // restore initial width
1107 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1109 pos.x += hud_fontsize.x * 0.25;
1110 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1111 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1112 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1114 pos.y += hud_fontsize.y;
1119 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1120 float stat_secrets_found, stat_secrets_total;
1121 float stat_monsters_killed, stat_monsters_total;
1125 // get monster stats
1126 stat_monsters_killed = STAT(MONSTERS_KILLED);
1127 stat_monsters_total = STAT(MONSTERS_TOTAL);
1128 stat_monsters_killed = 14;
1129 stat_monsters_total = 22;
1131 // get secrets stats
1132 stat_secrets_found = STAT(SECRETS_FOUND);
1133 stat_secrets_total = STAT(SECRETS_TOTAL);
1134 stat_secrets_found = 5;
1135 stat_secrets_total = 7;
1137 // get number of rows
1138 if(stat_secrets_total)
1140 if(stat_monsters_total)
1143 // if no rows, return
1147 // draw table header
1148 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1149 pos.y += 1.25 * hud_fontsize.y;
1150 pos.y += panel_bg_border;
1153 panel_size.y = hud_fontsize.y * rows;
1154 panel_size.y += panel_bg_padding * 2;
1155 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1157 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1159 if(panel_bg_padding)
1161 panel_pos += '1 1 0' * panel_bg_padding;
1162 panel_size -= '2 2 0' * panel_bg_padding;
1166 vector tmp = panel_size;
1169 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1172 if(stat_monsters_total)
1174 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1175 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1179 if(stat_secrets_total)
1181 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1182 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1185 panel_size.x += panel_bg_padding * 2; // restore initial width
1190 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1193 RANKINGS_RECEIVED_CNT = 0;
1194 for (i=RANKINGS_CNT-1; i>=0; --i)
1196 ++RANKINGS_RECEIVED_CNT;
1198 if (RANKINGS_RECEIVED_CNT == 0)
1201 vector hl_rgb = rgb + '0.5 0.5 0.5';
1203 pos.y += hud_fontsize.y;
1204 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1205 pos.y += 1.25 * hud_fontsize.y;
1206 pos.y += panel_bg_border;
1209 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1210 panel_size.y += panel_bg_padding * 2;
1211 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1213 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1215 if(panel_bg_padding)
1217 panel_pos += '1 1 0' * panel_bg_padding;
1218 panel_size -= '2 2 0' * panel_bg_padding;
1222 vector tmp = panel_size;
1225 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1228 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1235 n = grecordholder[i];
1236 p = count_ordinal(i+1);
1237 if(grecordholder[i] == entcs_GetName(player_localnum))
1238 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1239 else if(!(i % 2) && sbt_highlight)
1240 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1241 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1242 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);
1243 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1244 pos.y += 1.25 * hud_fontsize.y;
1247 panel_size.x += panel_bg_padding * 2; // restore initial width
1251 void Scoreboard_Draw()
1253 if(!autocvar__hud_configure)
1255 if(scoreboard_active) {
1256 if(menu_enabled == 1)
1257 scoreboard_fade_alpha = 1;
1258 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1259 if (scoreboard_fadeinspeed)
1260 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1262 scoreboard_fade_alpha = 1;
1265 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1266 if (scoreboard_fadeoutspeed)
1267 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1269 scoreboard_fade_alpha = 0;
1272 if (!scoreboard_fade_alpha)
1276 scoreboard_fade_alpha = 0;
1278 if (autocvar_hud_panel_scoreboard_dynamichud)
1281 HUD_Scale_Disable();
1283 float hud_fade_alpha_save = hud_fade_alpha;
1284 if(menu_enabled == 1)
1287 hud_fade_alpha = scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1288 HUD_Panel_UpdateCvars();
1290 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1291 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1292 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1293 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1294 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1295 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1297 hud_fade_alpha = hud_fade_alpha_save;
1299 // don't overlap with con_notify
1300 if(!autocvar__hud_configure)
1301 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1303 Scoreboard_UpdatePlayerTeams();
1309 // Initializes position
1313 vector sb_heading_fontsize;
1314 sb_heading_fontsize = hud_fontsize * 2;
1315 draw_beginBoldFont();
1316 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1319 pos.y += sb_heading_fontsize.y;
1320 pos.y += panel_bg_border;
1322 // Draw the scoreboard
1323 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1326 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1330 vector team_score_baseoffset = eY * hud_fontsize.y - eX * (panel_bg_border + hud_fontsize.x * 0.5);
1331 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1333 if(tm.team == NUM_SPECTATOR)
1335 if(!tm.team && teamplay)
1338 draw_beginBoldFont();
1339 vector rgb = Team_ColorRGB(tm.team);
1340 str = ftos(tm.(teamscores[ts_primary]));
1341 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1343 if(ts_primary != ts_secondary)
1345 str = ftos(tm.(teamscores[ts_secondary]));
1346 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);
1350 panel_bg_color = rgb * panel_bg_color_team;
1351 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1353 panel_bg_color = Team_ColorRGB(myteam) * panel_bg_color_team;
1357 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1359 if(tm.team == NUM_SPECTATOR)
1361 if(!tm.team && teamplay)
1364 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1368 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1369 if(race_speedaward) {
1370 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);
1371 pos.y += 1.25 * hud_fontsize.y;
1373 if(race_speedaward_alltimebest) {
1374 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);
1375 pos.y += 1.25 * hud_fontsize.y;
1377 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1379 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1380 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1382 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1387 for(pl = players.sort_next; pl; pl = pl.sort_next)
1389 if(pl.team != NUM_SPECTATOR)
1391 pos.y += 1.25 * hud_fontsize.y;
1392 Scoreboard_DrawItem(pos, panel_bg_color, pl, (pl.sv_entnum == player_localnum), specs);
1398 draw_beginBoldFont();
1399 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1401 pos.y += 1.25 * hud_fontsize.y;
1404 // Print info string
1406 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1407 tl = STAT(TIMELIMIT);
1408 fl = STAT(FRAGLIMIT);
1409 ll = STAT(LEADLIMIT);
1410 if(gametype == MAPINFO_TYPE_LMS)
1413 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1418 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1422 str = strcat(str, _(" or"));
1425 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], fl),
1426 (teamscores_label[ts_primary] == "score") ? CTX(_("SCO^points")) :
1427 (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1428 TranslateScoresLabel(teamscores_label[ts_primary])));
1432 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags[ps_primary], fl),
1433 (scores_label[ps_primary] == "score") ? CTX(_("SCO^points")) :
1434 (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1435 TranslateScoresLabel(scores_label[ps_primary])));
1440 if(tl > 0 || fl > 0)
1441 str = strcat(str, _(" or"));
1444 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], ll),
1445 (teamscores_label[ts_primary] == "score") ? CTX(_("SCO^points")) :
1446 (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1447 TranslateScoresLabel(teamscores_label[ts_primary])));
1451 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags[ps_primary], ll),
1452 (scores_label[ps_primary] == "score") ? CTX(_("SCO^points")) :
1453 (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1454 TranslateScoresLabel(scores_label[ps_primary])));
1459 pos.y += 1.2 * hud_fontsize.y;
1460 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1462 // print information about respawn status
1463 float respawn_time = STAT(RESPAWN_TIME);
1467 if(respawn_time < 0)
1469 // a negative number means we are awaiting respawn, time value is still the same
1470 respawn_time *= -1; // remove mark now that we checked it
1471 respawn_time = max(time, respawn_time); // don't show a negative value while the server is respawning the player (lag)
1473 str = sprintf(_("^1Respawning in ^3%s^1..."),
1474 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1475 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1477 count_seconds(respawn_time - time)
1481 else if(time < respawn_time)
1483 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1484 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1485 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1487 count_seconds(respawn_time - time)
1491 else if(time >= respawn_time)
1492 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1494 pos.y += 1.2 * hud_fontsize.y;
1495 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1498 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;