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 // TODO init autocvars
19 float autocvar_hud_panel_scoreboard_table_bg_alpha;
20 float autocvar_hud_panel_scoreboard_table_fg_alpha;
21 float autocvar_hud_panel_scoreboard_table_fg_alpha_self;
22 bool autocvar_hud_panel_scoreboard_table_highlight;
23 float autocvar_hud_panel_scoreboard_table_highlight_alpha;
24 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self;
26 void drawstringright(vector, string, vector, vector, float, float);
27 void drawstringcenter(vector, string, vector, vector, float, float);
29 // wrapper to put all possible scores titles through gettext
30 string TranslateScoresLabel(string l)
34 case "bckills": return CTX(_("SCO^bckills"));
35 case "bctime": return CTX(_("SCO^bctime"));
36 case "caps": return CTX(_("SCO^caps"));
37 case "captime": return CTX(_("SCO^captime"));
38 case "deaths": return CTX(_("SCO^deaths"));
39 case "destroyed": return CTX(_("SCO^destroyed"));
40 case "dmg": return CTX(_("SCO^dmg"));
41 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
42 case "drops": return CTX(_("SCO^drops"));
43 case "faults": return CTX(_("SCO^faults"));
44 case "fckills": return CTX(_("SCO^fckills"));
45 case "goals": return CTX(_("SCO^goals"));
46 case "kckills": return CTX(_("SCO^kckills"));
47 case "kdratio": return CTX(_("SCO^kdratio"));
48 case "k/d": return CTX(_("SCO^k/d"));
49 case "kd": return CTX(_("SCO^kd"));
50 case "kdr": return CTX(_("SCO^kdr"));
51 case "kills": return CTX(_("SCO^kills"));
52 case "laps": return CTX(_("SCO^laps"));
53 case "lives": return CTX(_("SCO^lives"));
54 case "losses": return CTX(_("SCO^losses"));
55 case "name": return CTX(_("SCO^name"));
56 case "sum": return CTX(_("SCO^sum"));
57 case "nick": return CTX(_("SCO^nick"));
58 case "objectives": return CTX(_("SCO^objectives"));
59 case "pickups": return CTX(_("SCO^pickups"));
60 case "ping": return CTX(_("SCO^ping"));
61 case "pl": return CTX(_("SCO^pl"));
62 case "pushes": return CTX(_("SCO^pushes"));
63 case "rank": return CTX(_("SCO^rank"));
64 case "returns": return CTX(_("SCO^returns"));
65 case "revivals": return CTX(_("SCO^revivals"));
66 case "score": return CTX(_("SCO^score"));
67 case "suicides": return CTX(_("SCO^suicides"));
68 case "takes": return CTX(_("SCO^takes"));
69 case "ticks": return CTX(_("SCO^ticks"));
78 ps_primary = ps_secondary = ts_primary = ts_secondary = -1;
79 for(i = 0; i < MAX_SCORE; ++i)
81 f = (scores_flags[i] & SFL_SORT_PRIO_MASK);
82 if(f == SFL_SORT_PRIO_PRIMARY)
84 if(f == SFL_SORT_PRIO_SECONDARY)
87 if(ps_secondary == -1)
88 ps_secondary = ps_primary;
90 for(i = 0; i < MAX_TEAMSCORE; ++i)
92 f = (teamscores_flags[i] & SFL_SORT_PRIO_MASK);
93 if(f == SFL_SORT_PRIO_PRIMARY)
95 if(f == SFL_SORT_PRIO_SECONDARY)
98 if(ts_secondary == -1)
99 ts_secondary = ts_primary;
101 Cmd_Scoreboard_SetFields(0);
104 float SetTeam(entity pl, float Team);
106 void Scoreboard_UpdatePlayerTeams()
113 for(pl = players.sort_next; pl; pl = pl.sort_next)
116 Team = entcs_GetScoreTeam(pl.sv_entnum);
117 if(SetTeam(pl, Team))
120 HUD_UpdatePlayerPos(pl);
124 pl = players.sort_next;
129 print(strcat("PNUM: ", ftos(num), "\n"));
134 int HUD_CompareScore(int vl, int vr, int f)
136 TC(int, vl); TC(int, vr); TC(int, f);
137 if(f & SFL_ZERO_IS_WORST)
139 if(vl == 0 && vr != 0)
141 if(vl != 0 && vr == 0)
145 return IS_INCREASING(f);
147 return IS_DECREASING(f);
151 float HUD_ComparePlayerScores(entity left, entity right)
154 vl = entcs_GetTeam(left.sv_entnum);
155 vr = entcs_GetTeam(right.sv_entnum);
167 if(vl == NUM_SPECTATOR)
169 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
171 if(!left.gotscores && right.gotscores)
176 r = HUD_CompareScore(left.scores[ps_primary], right.scores[ps_primary], scores_flags[ps_primary]);
180 r = HUD_CompareScore(left.scores[ps_secondary], right.scores[ps_secondary], scores_flags[ps_secondary]);
185 for(i = 0; i < MAX_SCORE; ++i)
187 r = HUD_CompareScore(left.scores[i], right.scores[i], scores_flags[i]);
192 if (left.sv_entnum < right.sv_entnum)
198 void HUD_UpdatePlayerPos(entity player)
201 for(ent = player.sort_next; ent && HUD_ComparePlayerScores(player, ent); ent = player.sort_next)
203 SORT_SWAP(player, ent);
205 for(ent = player.sort_prev; ent != players && HUD_ComparePlayerScores(ent, player); ent = player.sort_prev)
207 SORT_SWAP(ent, player);
211 float HUD_CompareTeamScores(entity left, entity right)
215 if(left.team == NUM_SPECTATOR)
217 if(right.team == NUM_SPECTATOR)
220 r = HUD_CompareScore(left.teamscores[ts_primary], right.teamscores[ts_primary], teamscores_flags[ts_primary]);
224 r = HUD_CompareScore(left.teamscores[ts_secondary], right.teamscores[ts_secondary], teamscores_flags[ts_secondary]);
228 for(i = 0; i < MAX_SCORE; ++i)
230 r = HUD_CompareScore(left.teamscores[i], right.teamscores[i], teamscores_flags[i]);
235 if (left.team < right.team)
241 void HUD_UpdateTeamPos(entity Team)
244 for(ent = Team.sort_next; ent && HUD_CompareTeamScores(Team, ent); ent = Team.sort_next)
246 SORT_SWAP(Team, ent);
248 for(ent = Team.sort_prev; ent != teams && HUD_CompareTeamScores(ent, Team); ent = Team.sort_prev)
250 SORT_SWAP(ent, Team);
254 void Cmd_Scoreboard_Help()
256 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
257 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
258 LOG_INFO(_("Usage:\n"));
259 LOG_INFO(_("^2scoreboard_columns_set default\n"));
260 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
261 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
262 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n\n"));
264 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
265 LOG_INFO(_("^3ping^7 Ping time\n"));
266 LOG_INFO(_("^3pl^7 Packet loss\n"));
267 LOG_INFO(_("^3kills^7 Number of kills\n"));
268 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
269 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
270 LOG_INFO(_("^3frags^7 kills - suicides\n"));
271 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
272 LOG_INFO(_("^3dmg^7 The total damage done\n"));
273 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
274 LOG_INFO(_("^3sum^7 frags - deaths\n"));
275 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
276 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
277 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
278 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
279 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
280 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
281 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
282 LOG_INFO(_("^3rank^7 Player rank\n"));
283 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
284 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
285 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
286 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
287 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
288 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
289 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
290 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
291 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
292 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
293 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
294 LOG_INFO(_("^3score^7 Total score\n\n"));
296 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
297 "of game types, then a slash, to make the field show up only in these\n"
298 "or in all but these game types. You can also specify 'all' as a\n"
299 "field to show all fields available for the current game mode.\n\n"));
301 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
302 "include/exclude ALL teams/noteams game modes.\n\n"));
304 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
305 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
306 "right of the vertical bar aligned to the right.\n"));
307 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
308 "other gamemodes except DM.\n"));
311 // NOTE: adding a gametype with ? to not warn for an optional field
312 // make sure it's excluded in a previous exclusive rule, if any
313 // otherwise the previous exclusive rule warns anyway
314 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
315 #define SCOREBOARD_DEFAULT_COLUMNS \
317 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
318 " -teams,lms/deaths +ft,tdm/deaths" \
319 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
320 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
321 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
322 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
323 " +lms/lives +lms/rank" \
324 " +kh/caps +kh/pushes +kh/destroyed" \
325 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
326 " +as/objectives +nb/faults +nb/goals" \
327 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
328 " -lms,rc,cts,inv,nb/score"
330 void Cmd_Scoreboard_SetFields(int argc)
335 float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
340 // set up a temporary scoreboard layout
341 // no layout can be properly set up until score_info data haven't been received
342 argc = tokenizebyseparator("0 1 ping pl name | score", " ");
344 scores_label[ps_primary] = strzone("score");
345 scores_flags[ps_primary] = SFL_ALLOW_HIDE;
348 // TODO: re enable with gametype dependant cvars?
349 if(argc < 3) // no arguments provided
350 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
353 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
357 if(argv(2) == "default")
358 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
359 else if(argv(2) == "all")
362 s = "ping pl name |";
363 for(i = 0; i < MAX_SCORE; ++i)
366 if(i != ps_secondary)
367 if(scores_label[i] != "")
368 s = strcat(s, " ", scores_label[i]);
370 if(ps_secondary != ps_primary)
371 s = strcat(s, " ", scores_label[ps_secondary]);
372 s = strcat(s, " ", scores_label[ps_primary]);
373 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
380 hud_fontsize = HUD_GetFontsize("hud_fontsize");
382 for(i = 1; i < argc - 1; ++i)
388 if(substring(str, 0, 1) == "?")
391 str = substring(str, 1, strlen(str) - 1);
394 slash = strstrofs(str, "/", 0);
397 pattern = substring(str, 0, slash);
398 str = substring(str, slash + 1, strlen(str) - (slash + 1));
400 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
404 strunzone(sbt_field_title[sbt_num_fields]);
405 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
406 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
407 str = strtolower(str);
411 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
412 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
413 case "kd": case "kdr": case "kdratio": case "k/d": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
414 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
415 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
416 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
417 case "dmg": sbt_field[sbt_num_fields] = SP_DMG; break;
418 case "dmgtaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
421 for(j = 0; j < MAX_SCORE; ++j)
422 if(str == strtolower(scores_label[j]))
423 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
431 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
435 sbt_field[sbt_num_fields] = j;
438 if(j == ps_secondary)
444 if(sbt_num_fields >= MAX_SBT_FIELDS)
448 if(scores_flags[ps_primary] & SFL_ALLOW_HIDE)
450 if(scores_flags[ps_secondary] & SFL_ALLOW_HIDE)
452 if(ps_primary == ps_secondary)
454 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
456 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
460 strunzone(sbt_field_title[sbt_num_fields]);
461 for(i = sbt_num_fields; i > 0; --i)
463 sbt_field_title[i] = sbt_field_title[i-1];
464 sbt_field_size[i] = sbt_field_size[i-1];
465 sbt_field[i] = sbt_field[i-1];
467 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
468 sbt_field[0] = SP_NAME;
470 LOG_INFO("fixed missing field 'name'\n");
474 strunzone(sbt_field_title[sbt_num_fields]);
475 for(i = sbt_num_fields; i > 1; --i)
477 sbt_field_title[i] = sbt_field_title[i-1];
478 sbt_field_size[i] = sbt_field_size[i-1];
479 sbt_field[i] = sbt_field[i-1];
481 sbt_field_title[1] = strzone("|");
482 sbt_field[1] = SP_SEPARATOR;
483 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
485 LOG_INFO("fixed missing field '|'\n");
488 else if(!have_separator)
490 strunzone(sbt_field_title[sbt_num_fields]);
491 sbt_field_title[sbt_num_fields] = strzone("|");
492 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
493 sbt_field[sbt_num_fields] = SP_SEPARATOR;
495 LOG_INFO("fixed missing field '|'\n");
499 strunzone(sbt_field_title[sbt_num_fields]);
500 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_secondary]));
501 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
502 sbt_field[sbt_num_fields] = ps_secondary;
504 LOG_INFOF("fixed missing field '%s'\n", scores_label[ps_secondary]);
508 strunzone(sbt_field_title[sbt_num_fields]);
509 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_primary]));
510 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
511 sbt_field[sbt_num_fields] = ps_primary;
513 LOG_INFOF("fixed missing field '%s'\n", scores_label[ps_primary]);
517 sbt_field[sbt_num_fields] = SP_END;
521 vector sbt_field_rgb;
522 string sbt_field_icon0;
523 string sbt_field_icon1;
524 string sbt_field_icon2;
525 vector sbt_field_icon0_rgb;
526 vector sbt_field_icon1_rgb;
527 vector sbt_field_icon2_rgb;
528 float sbt_field_icon0_alpha;
529 float sbt_field_icon1_alpha;
530 float sbt_field_icon2_alpha;
531 string HUD_GetField(entity pl, int field)
534 float tmp, num, denom;
537 sbt_field_rgb = '1 1 1';
538 sbt_field_icon0 = "";
539 sbt_field_icon1 = "";
540 sbt_field_icon2 = "";
541 sbt_field_icon0_rgb = '1 1 1';
542 sbt_field_icon1_rgb = '1 1 1';
543 sbt_field_icon2_rgb = '1 1 1';
544 sbt_field_icon0_alpha = 1;
545 sbt_field_icon1_alpha = 1;
546 sbt_field_icon2_alpha = 1;
551 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
552 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
556 tmp = max(0, min(220, f-80)) / 220;
557 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
563 f = pl.ping_packetloss;
564 tmp = pl.ping_movementloss;
565 if(f == 0 && tmp == 0)
567 str = ftos(ceil(f * 100));
569 str = strcat(str, "~", ftos(ceil(tmp * 100)));
570 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
571 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
575 if(ready_waiting && pl.ready)
577 sbt_field_icon0 = "gfx/scoreboard/player_ready";
581 f = stof(getplayerkeyvalue(pl.sv_entnum, "colors"));
583 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
584 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
585 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
586 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
587 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
590 return entcs_GetName(pl.sv_entnum);
593 f = pl.(scores[SP_KILLS]);
594 f -= pl.(scores[SP_SUICIDES]);
598 num = pl.(scores[SP_KILLS]);
599 denom = pl.(scores[SP_DEATHS]);
602 sbt_field_rgb = '0 1 0';
603 str = sprintf("%d", num);
604 } else if(num <= 0) {
605 sbt_field_rgb = '1 0 0';
606 str = sprintf("%.1f", num/denom);
608 str = sprintf("%.1f", num/denom);
612 f = pl.(scores[SP_KILLS]);
613 f -= pl.(scores[SP_DEATHS]);
616 sbt_field_rgb = '0 1 0';
618 sbt_field_rgb = '1 1 1';
620 sbt_field_rgb = '1 0 0';
625 num = pl.(scores[SP_DMG]);
628 str = sprintf("%.1f k", num/denom);
632 num = pl.(scores[SP_DMGTAKEN]);
635 str = sprintf("%.1f k", num/denom);
639 tmp = pl.(scores[field]);
640 f = scores_flags[field];
641 if(field == ps_primary)
642 sbt_field_rgb = '1 1 0';
643 else if(field == ps_secondary)
644 sbt_field_rgb = '0 1 1';
646 sbt_field_rgb = '1 1 1';
647 return ScoreString(f, tmp);
652 float sbt_fixcolumnwidth_len;
653 float sbt_fixcolumnwidth_iconlen;
654 float sbt_fixcolumnwidth_marginlen;
656 string HUD_FixScoreboardColumnWidth(int i, string str)
661 field = sbt_field[i];
663 sbt_fixcolumnwidth_iconlen = 0;
665 if(sbt_field_icon0 != "")
667 sz = draw_getimagesize(sbt_field_icon0);
669 if(sbt_fixcolumnwidth_iconlen < f)
670 sbt_fixcolumnwidth_iconlen = f;
673 if(sbt_field_icon1 != "")
675 sz = draw_getimagesize(sbt_field_icon1);
677 if(sbt_fixcolumnwidth_iconlen < f)
678 sbt_fixcolumnwidth_iconlen = f;
681 if(sbt_field_icon2 != "")
683 sz = draw_getimagesize(sbt_field_icon2);
685 if(sbt_fixcolumnwidth_iconlen < f)
686 sbt_fixcolumnwidth_iconlen = f;
689 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
691 if(sbt_fixcolumnwidth_iconlen != 0)
692 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
694 sbt_fixcolumnwidth_marginlen = 0;
696 if(field == SP_NAME) // name gets all remaining space
700 namesize = panel_size.x;
701 for(j = 0; j < sbt_num_fields; ++j)
703 if (sbt_field[i] != SP_SEPARATOR)
704 namesize -= sbt_field_size[j] + hud_fontsize.x;
705 namesize += hud_fontsize.x;
706 sbt_field_size[i] = namesize;
708 if (sbt_fixcolumnwidth_iconlen != 0)
709 namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen;
710 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
711 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
714 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
716 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen;
717 if(sbt_field_size[i] < f)
718 sbt_field_size[i] = f;
723 void HUD_PrintScoreboardItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
725 TC(bool, is_self); TC(int, pl_number);
729 is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
730 if(is_spec && !is_self)
733 vector h_pos = item_pos;
734 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
735 // alternated rows highlighting
737 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
738 else if((sbt_highlight) && (!(pl_number % 2)))
739 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
741 vector pos = item_pos;
742 vector tmp = '0 0 0';
744 for(i = 0; i < sbt_num_fields; ++i)
746 field = sbt_field[i];
747 if(field == SP_SEPARATOR)
750 if(is_spec && field != SP_NAME && field != SP_PING) {
751 pos.x += sbt_field_size[i] + hud_fontsize.x;
754 str = HUD_GetField(pl, field);
755 str = HUD_FixScoreboardColumnWidth(i, str);
757 pos.x += sbt_field_size[i] + hud_fontsize.x;
759 if(field == SP_NAME) {
760 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
762 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
764 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
766 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
768 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
770 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
773 tmp.x = sbt_field_size[i] + hud_fontsize.x;
774 if(sbt_field_icon0 != "")
776 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);
778 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);
779 if(sbt_field_icon1 != "")
781 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);
783 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);
784 if(sbt_field_icon2 != "")
786 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);
788 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);
791 if(sbt_field[i] == SP_SEPARATOR)
793 pos.x = item_pos.x + panel_size.x;
794 for(i = sbt_num_fields-1; i > 0; --i)
796 field = sbt_field[i];
797 if(field == SP_SEPARATOR)
800 if(is_spec && field != SP_NAME && field != SP_PING) {
801 pos.x -= sbt_field_size[i] + hud_fontsize.x;
805 str = HUD_GetField(pl, field);
806 str = HUD_FixScoreboardColumnWidth(i, str);
808 if(field == SP_NAME) {
809 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
811 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
813 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
815 tmp.x = sbt_fixcolumnwidth_len;
817 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
819 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
822 tmp.x = sbt_field_size[i];
823 if(sbt_field_icon0 != "")
825 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);
827 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);
828 if(sbt_field_icon1 != "")
830 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);
832 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);
833 if(sbt_field_icon2 != "")
835 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);
837 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);
838 pos.x -= sbt_field_size[i] + hud_fontsize.x;
843 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
846 vector HUD_Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
848 vector column_dim = '0 0 0';
852 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
853 panel_size.y += panel_bg_padding * 2;
854 HUD_Panel_DrawBg(scoreboard_fade_alpha);
856 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
860 panel_pos += '1 1 0' * panel_bg_padding;
861 panel_size -= '2 2 0' * panel_bg_padding;
865 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
869 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
871 pos.y += 1.25 * hud_fontsize.y;
874 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
876 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
878 // go back to the top to make alternated columns highlighting and to print the strings
879 pos.y -= 1.25 * hud_fontsize.y;
882 column_dim.y = panel_size.y;
884 // print the strings of the columns headers and draw the columns
886 for(i = 0; i < sbt_num_fields; ++i)
888 if(sbt_field[i] == SP_SEPARATOR)
890 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
894 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
896 drawstring(pos, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
897 pos.x += column_dim.x;
899 if(sbt_field[i] == SP_SEPARATOR)
901 pos.x = panel_pos.x + panel_size.x;
903 for(i = sbt_num_fields - 1; i > 0; --i)
905 if(sbt_field[i] == SP_SEPARATOR)
908 pos.x -= sbt_field_size[i];
914 if (i == sbt_num_fields-1)
915 column_dim.x = sbt_field_size[i] + hud_fontsize.x * 0.5;
917 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
918 if(i == sbt_num_fields - 1)
919 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
921 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
925 tmp.x = stringwidth(sbt_field_title[i], false, hud_fontsize);
926 tmp.x = (sbt_field_size[i] - tmp.x);
927 drawstring(pos + tmp, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
928 pos.x -= hud_fontsize.x;
933 pos.y += 1.25 * hud_fontsize.y; // skip the header
936 tmp.x = panel_size.x;
937 tmp.y = hud_fontsize.y * 1.25;
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 HUD_PrintScoreboardItem(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 HUD_PrintScoreboardItem(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 HUD_WouldDrawScoreboard() {
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 HUD_DrawScoreboardAccuracyStats(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_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);
1033 // column highlighting
1034 for (int i = 0; i < columnns; ++i)
1036 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1039 for (int i = 0; i < rows; ++i)
1040 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1042 average_accuracy = 0;
1043 int weapons_with_stats = 0;
1045 pos.x += weapon_width / 2;
1047 if (autocvar_scoreboard_accuracy_nocolors)
1050 Accuracy_LoadColors();
1052 float oldposx = pos.x;
1056 FOREACH(Weapons, it != WEP_Null, {
1057 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1059 WepSet set = it.m_wepset;
1060 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1064 if (weapon_stats >= 0)
1065 weapon_alpha = sbt_fg_alpha;
1067 weapon_alpha = 0.2 * sbt_fg_alpha;
1070 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1072 if (weapon_stats >= 0) {
1073 weapons_with_stats += 1;
1074 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1077 s = sprintf("%d%%", weapon_stats * 100);
1080 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1082 if(!autocvar_scoreboard_accuracy_nocolors)
1083 rgb = Accuracy_GetColor(weapon_stats);
1085 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1087 tmpos.x += weapon_width * rows;
1088 pos.x += weapon_width * rows;
1089 if (rows == 2 && column == columnns - 1) {
1097 if (weapons_with_stats)
1098 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1100 panel_size.x += panel_bg_padding * 2; // restore initial width
1104 vector HUD_DrawKeyValue(vector pos, string key, string value) {
1106 pos.x += hud_fontsize.x * 0.25;
1107 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1108 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1109 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1111 pos.y += hud_fontsize.y;
1116 vector HUD_DrawMapStats(vector pos, vector rgb, vector bg_size) {
1117 float stat_secrets_found, stat_secrets_total;
1118 float stat_monsters_killed, stat_monsters_total;
1122 // get monster stats
1123 stat_monsters_killed = STAT(MONSTERS_KILLED);
1124 stat_monsters_total = STAT(MONSTERS_TOTAL);
1126 // get secrets stats
1127 stat_secrets_found = STAT(SECRETS_FOUND);
1128 stat_secrets_total = STAT(SECRETS_TOTAL);
1130 // get number of rows
1131 if(stat_secrets_total)
1133 if(stat_monsters_total)
1136 // if no rows, return
1140 // draw table header
1141 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1142 pos.y += 1.25 * hud_fontsize.y;
1143 pos.y += panel_bg_border;
1146 panel_size.y = hud_fontsize.y * rows;
1147 panel_size.y += panel_bg_padding * 2;
1148 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1150 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1152 if(panel_bg_padding)
1154 panel_pos += '1 1 0' * panel_bg_padding;
1155 panel_size -= '2 2 0' * panel_bg_padding;
1159 vector tmp = panel_size;
1162 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1165 if(stat_monsters_total)
1167 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1168 pos = HUD_DrawKeyValue(pos, _("Monsters killed:"), val);
1172 if(stat_secrets_total)
1174 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1175 pos = HUD_DrawKeyValue(pos, _("Secrets found:"), val);
1178 panel_size.x += panel_bg_padding * 2; // restore initial width
1183 vector HUD_DrawScoreboardRankings(vector pos, entity pl, vector rgb, vector bg_size)
1186 RANKINGS_RECEIVED_CNT = 0;
1187 for (i=RANKINGS_CNT-1; i>=0; --i)
1189 ++RANKINGS_RECEIVED_CNT;
1191 if (RANKINGS_RECEIVED_CNT == 0)
1194 vector hl_rgb = rgb + '0.5 0.5 0.5';
1196 pos.y += hud_fontsize.y;
1197 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1198 pos.y += 1.25 * hud_fontsize.y;
1199 pos.y += panel_bg_border;
1202 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1203 panel_size.y += panel_bg_padding * 2;
1204 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1206 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1208 if(panel_bg_padding)
1210 panel_pos += '1 1 0' * panel_bg_padding;
1211 panel_size -= '2 2 0' * panel_bg_padding;
1215 vector tmp = panel_size;
1218 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1221 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1228 n = grecordholder[i];
1229 p = count_ordinal(i+1);
1230 if(grecordholder[i] == entcs_GetName(player_localnum))
1231 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1232 else if(!(i % 2) && sbt_highlight)
1233 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1234 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1235 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);
1236 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1237 pos.y += 1.25 * hud_fontsize.y;
1240 panel_size.x += panel_bg_padding * 2; // restore initial width
1244 float hud_woulddrawscoreboard_prev;
1245 float hud_woulddrawscoreboard_change; // "time" at which HUD_WouldDrawScoreboard() changed
1246 void HUD_DrawScoreboard()
1248 if(!autocvar__hud_configure)
1250 float hud_woulddrawscoreboard;
1251 hud_woulddrawscoreboard = scoreboard_active;
1252 if(hud_woulddrawscoreboard != hud_woulddrawscoreboard_prev) {
1253 hud_woulddrawscoreboard_change = time;
1254 hud_woulddrawscoreboard_prev = hud_woulddrawscoreboard;
1257 if(hud_woulddrawscoreboard) {
1258 if(menu_enabled == 1)
1259 scoreboard_fade_alpha = 1;
1260 float scoreboard_fadeinspeed = autocvar_scoreboard_fadeinspeed;
1261 if (scoreboard_fadeinspeed)
1262 scoreboard_fade_alpha = bound (0, (time - hud_woulddrawscoreboard_change) * scoreboard_fadeinspeed, 1);
1264 scoreboard_fade_alpha = 1;
1267 float scoreboard_fadeoutspeed = autocvar_scoreboard_fadeoutspeed;
1268 if (scoreboard_fadeoutspeed)
1269 scoreboard_fade_alpha = bound (0, (1/scoreboard_fadeoutspeed - (time - hud_woulddrawscoreboard_change)) * scoreboard_fadeoutspeed, 1);
1271 scoreboard_fade_alpha = 0;
1274 if (!scoreboard_fade_alpha)
1278 scoreboard_fade_alpha = 0;
1280 if (autocvar_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 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * ((autocvar_scoreboard_bg_scale > 0) ? autocvar_scoreboard_bg_scale : 0.25);
1329 vector team_score_baseoffset = eY * hud_fontsize.y - eX * (panel_bg_border + hud_fontsize.x * 0.5);
1330 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1332 if(tm.team == NUM_SPECTATOR)
1334 if(!tm.team && teamplay)
1337 draw_beginBoldFont();
1338 vector rgb = Team_ColorRGB(tm.team);
1339 str = ftos(tm.(teamscores[ts_primary]));
1340 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1342 if(ts_primary != ts_secondary)
1344 str = ftos(tm.(teamscores[ts_secondary]));
1345 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);
1349 panel_bg_color = rgb * panel_bg_color_team;
1350 pos = HUD_Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1352 panel_bg_color = Team_ColorRGB(myteam) * panel_bg_color_team;
1356 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1358 if(tm.team == NUM_SPECTATOR)
1360 if(!tm.team && teamplay)
1363 pos = HUD_Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1367 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1368 if(race_speedaward) {
1369 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);
1370 pos.y += 1.25 * hud_fontsize.y;
1372 if(race_speedaward_alltimebest) {
1373 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);
1374 pos.y += 1.25 * hud_fontsize.y;
1376 pos = HUD_DrawScoreboardRankings(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1378 else if (autocvar_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL) {
1379 pos = HUD_DrawScoreboardAccuracyStats(pos, panel_bg_color, bg_size);
1382 pos = HUD_DrawMapStats(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 HUD_PrintScoreboardItem(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_scoreboard_respawntime_decimals ?
1475 count_seconds_decs(respawn_time - time, autocvar_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_scoreboard_respawntime_decimals ?
1485 count_seconds_decs(respawn_time - time, autocvar_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;