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 vector HUD_PrintScoreboardHeader(vector pos, vector rgb)
726 vector column_dim = eY * panel_size.y;
727 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
728 for(i = 0; i < sbt_num_fields; ++i)
730 if(sbt_field[i] == SP_SEPARATOR)
732 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
735 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
736 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
737 pos.x += column_dim.x;
739 if(sbt_field[i] == SP_SEPARATOR)
741 pos.x = panel_pos.x + panel_size.x;
742 for(i = sbt_num_fields - 1; i > 0; --i)
744 if(sbt_field[i] == SP_SEPARATOR)
747 pos.x -= sbt_field_size[i];
752 if (i == sbt_num_fields-1)
753 column_dim.x = sbt_field_size[i] + hud_fontsize.x * 0.5;
755 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
756 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
759 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
760 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
761 pos.x -= hud_fontsize.x;
766 pos.y += 1.25 * hud_fontsize.y;
770 void HUD_PrintScoreboardItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
772 TC(bool, is_self); TC(int, pl_number);
776 is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
777 if(is_spec && !is_self)
780 vector h_pos = item_pos;
781 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
782 // alternated rows highlighting
784 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
785 else if((sbt_highlight) && (!(pl_number % 2)))
786 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
788 vector pos = item_pos;
789 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
790 vector tmp = '0 0 0';
792 for(i = 0; i < sbt_num_fields; ++i)
794 field = sbt_field[i];
795 if(field == SP_SEPARATOR)
798 if(is_spec && field != SP_NAME && field != SP_PING) {
799 pos.x += sbt_field_size[i] + hud_fontsize.x;
802 str = HUD_GetField(pl, field);
803 str = HUD_FixScoreboardColumnWidth(i, str);
805 pos.x += sbt_field_size[i] + hud_fontsize.x;
807 if(field == SP_NAME) {
808 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
810 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
812 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
814 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
816 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
818 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
821 tmp.x = sbt_field_size[i] + hud_fontsize.x;
822 if(sbt_field_icon0 != "")
824 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);
826 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);
827 if(sbt_field_icon1 != "")
829 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);
831 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);
832 if(sbt_field_icon2 != "")
834 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);
836 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);
839 if(sbt_field[i] == SP_SEPARATOR)
841 pos.x = item_pos.x + panel_size.x;
842 for(i = sbt_num_fields-1; i > 0; --i)
844 field = sbt_field[i];
845 if(field == SP_SEPARATOR)
848 if(is_spec && field != SP_NAME && field != SP_PING) {
849 pos.x -= sbt_field_size[i] + hud_fontsize.x;
853 str = HUD_GetField(pl, field);
854 str = HUD_FixScoreboardColumnWidth(i, str);
856 if(field == SP_NAME) {
857 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
859 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
861 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
863 tmp.x = sbt_fixcolumnwidth_len;
865 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
867 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
870 tmp.x = sbt_field_size[i];
871 if(sbt_field_icon0 != "")
873 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);
875 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);
876 if(sbt_field_icon1 != "")
878 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);
880 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);
881 if(sbt_field_icon2 != "")
883 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
885 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);
886 pos.x -= sbt_field_size[i] + hud_fontsize.x;
891 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
894 vector HUD_Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
899 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
900 panel_size.y += panel_bg_padding * 2;
901 HUD_Panel_DrawBg(scoreboard_fade_alpha);
903 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
907 panel_pos += '1 1 0' * panel_bg_padding;
908 panel_size -= '2 2 0' * panel_bg_padding;
912 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
916 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
918 pos.y += 1.25 * hud_fontsize.y;
921 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
923 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
926 // print header row and highlight columns
927 pos = HUD_PrintScoreboardHeader(panel_pos, rgb);
929 // fill the table and draw the rows
932 for(pl = players.sort_next; pl; pl = pl.sort_next)
934 if(pl.team != tm.team)
936 HUD_PrintScoreboardItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
937 pos.y += 1.25 * hud_fontsize.y;
941 for(pl = players.sort_next; pl; pl = pl.sort_next)
943 if(pl.team == NUM_SPECTATOR)
945 HUD_PrintScoreboardItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
946 pos.y += 1.25 * hud_fontsize.y;
950 panel_size.x += panel_bg_padding * 2; // restore initial width
954 float HUD_WouldDrawScoreboard() {
955 if (QuickMenu_IsOpened())
957 else if (HUD_Radar_Clickable())
959 else if (scoreboard_showscores)
961 else if (intermission == 1)
963 else if (intermission == 2)
965 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
967 else if (scoreboard_showscores_force)
972 float average_accuracy;
973 vector HUD_DrawScoreboardAccuracyStats(vector pos, vector rgb, vector bg_size)
975 WepSet weapons_stat = WepSet_GetFromStat();
976 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
978 FOREACH(Weapons, it != WEP_Null, {
979 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
981 WepSet set = it.m_wepset;
982 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
986 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
987 if (weapon_cnt <= 0) return pos;
990 if (autocvar_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
992 int columnns = ceil(weapon_cnt / rows);
996 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
997 pos.y += 1.25 * hud_fontsize.y;
998 pos.y += panel_bg_border;
1001 panel_size.y = height * rows;
1002 panel_size.y += panel_bg_padding * 2;
1003 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1005 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1007 if(panel_bg_padding)
1009 panel_pos += '1 1 0' * panel_bg_padding;
1010 panel_size -= '2 2 0' * panel_bg_padding;
1014 vector tmp = panel_size;
1016 float fontsize = height * 1/3;
1017 float weapon_height = height * 2/3;
1018 float weapon_width = tmp.x / columnns / rows;
1021 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1025 // column highlighting
1026 for (int i = 0; i < columnns; ++i)
1028 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1031 for (int i = 0; i < rows; ++i)
1032 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1035 average_accuracy = 0;
1036 int weapons_with_stats = 0;
1038 pos.x += weapon_width / 2;
1040 if (autocvar_scoreboard_accuracy_nocolors)
1043 Accuracy_LoadColors();
1045 float oldposx = pos.x;
1049 FOREACH(Weapons, it != WEP_Null, {
1050 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1052 WepSet set = it.m_wepset;
1053 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1057 if (weapon_stats >= 0)
1058 weapon_alpha = sbt_fg_alpha;
1060 weapon_alpha = 0.2 * sbt_fg_alpha;
1063 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1065 if (weapon_stats >= 0) {
1066 weapons_with_stats += 1;
1067 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1070 s = sprintf("%d%%", weapon_stats * 100);
1073 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1075 if(!autocvar_scoreboard_accuracy_nocolors)
1076 rgb = Accuracy_GetColor(weapon_stats);
1078 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1080 tmpos.x += weapon_width * rows;
1081 pos.x += weapon_width * rows;
1082 if (rows == 2 && column == columnns - 1) {
1090 if (weapons_with_stats)
1091 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1093 panel_size.x += panel_bg_padding * 2; // restore initial width
1097 vector HUD_DrawKeyValue(vector pos, string key, string value) {
1099 pos.x += hud_fontsize.x * 0.25;
1100 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1101 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1102 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1104 pos.y += hud_fontsize.y;
1109 vector HUD_DrawMapStats(vector pos, vector rgb, vector bg_size) {
1110 float stat_secrets_found, stat_secrets_total;
1111 float stat_monsters_killed, stat_monsters_total;
1115 // get monster stats
1116 stat_monsters_killed = STAT(MONSTERS_KILLED);
1117 stat_monsters_total = STAT(MONSTERS_TOTAL);
1119 // get secrets stats
1120 stat_secrets_found = STAT(SECRETS_FOUND);
1121 stat_secrets_total = STAT(SECRETS_TOTAL);
1123 // get number of rows
1124 if(stat_secrets_total)
1126 if(stat_monsters_total)
1129 // if no rows, return
1133 // draw table header
1134 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1135 pos.y += 1.25 * hud_fontsize.y;
1136 pos.y += panel_bg_border;
1139 panel_size.y = hud_fontsize.y * rows;
1140 panel_size.y += panel_bg_padding * 2;
1141 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1143 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1145 if(panel_bg_padding)
1147 panel_pos += '1 1 0' * panel_bg_padding;
1148 panel_size -= '2 2 0' * panel_bg_padding;
1152 vector tmp = panel_size;
1155 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1158 if(stat_monsters_total)
1160 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1161 pos = HUD_DrawKeyValue(pos, _("Monsters killed:"), val);
1165 if(stat_secrets_total)
1167 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1168 pos = HUD_DrawKeyValue(pos, _("Secrets found:"), val);
1171 panel_size.x += panel_bg_padding * 2; // restore initial width
1176 vector HUD_DrawScoreboardRankings(vector pos, entity pl, vector rgb, vector bg_size)
1179 RANKINGS_RECEIVED_CNT = 0;
1180 for (i=RANKINGS_CNT-1; i>=0; --i)
1182 ++RANKINGS_RECEIVED_CNT;
1184 if (RANKINGS_RECEIVED_CNT == 0)
1187 vector hl_rgb = rgb + '0.5 0.5 0.5';
1189 pos.y += hud_fontsize.y;
1190 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1191 pos.y += 1.25 * hud_fontsize.y;
1192 pos.y += panel_bg_border;
1195 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1196 panel_size.y += panel_bg_padding * 2;
1197 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1199 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1201 if(panel_bg_padding)
1203 panel_pos += '1 1 0' * panel_bg_padding;
1204 panel_size -= '2 2 0' * panel_bg_padding;
1208 vector tmp = panel_size;
1211 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1214 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1221 n = grecordholder[i];
1222 p = count_ordinal(i+1);
1223 if(grecordholder[i] == entcs_GetName(player_localnum))
1224 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1225 else if(!(i % 2) && sbt_highlight)
1226 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1227 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1228 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);
1229 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1230 pos.y += 1.25 * hud_fontsize.y;
1233 panel_size.x += panel_bg_padding * 2; // restore initial width
1237 float hud_woulddrawscoreboard_prev;
1238 float hud_woulddrawscoreboard_change; // "time" at which HUD_WouldDrawScoreboard() changed
1239 void HUD_DrawScoreboard()
1241 if(!autocvar__hud_configure)
1243 float hud_woulddrawscoreboard;
1244 hud_woulddrawscoreboard = scoreboard_active;
1245 if(hud_woulddrawscoreboard != hud_woulddrawscoreboard_prev) {
1246 hud_woulddrawscoreboard_change = time;
1247 hud_woulddrawscoreboard_prev = hud_woulddrawscoreboard;
1250 if(hud_woulddrawscoreboard) {
1251 if(menu_enabled == 1)
1252 scoreboard_fade_alpha = 1;
1253 float scoreboard_fadeinspeed = autocvar_scoreboard_fadeinspeed;
1254 if (scoreboard_fadeinspeed)
1255 scoreboard_fade_alpha = bound (0, (time - hud_woulddrawscoreboard_change) * scoreboard_fadeinspeed, 1);
1257 scoreboard_fade_alpha = 1;
1260 float scoreboard_fadeoutspeed = autocvar_scoreboard_fadeoutspeed;
1261 if (scoreboard_fadeoutspeed)
1262 scoreboard_fade_alpha = bound (0, (1/scoreboard_fadeoutspeed - (time - hud_woulddrawscoreboard_change)) * scoreboard_fadeoutspeed, 1);
1264 scoreboard_fade_alpha = 0;
1267 if (!scoreboard_fade_alpha)
1271 scoreboard_fade_alpha = 0;
1273 if (autocvar_scoreboard_dynamichud)
1276 HUD_Scale_Disable();
1278 float hud_fade_alpha_save = hud_fade_alpha;
1279 if(menu_enabled == 1)
1282 hud_fade_alpha = scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1283 HUD_Panel_UpdateCvars();
1285 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1286 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1287 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1288 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1289 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1290 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1292 hud_fade_alpha = hud_fade_alpha_save;
1294 // don't overlap with con_notify
1295 if(!autocvar__hud_configure)
1296 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1298 Scoreboard_UpdatePlayerTeams();
1304 // Initializes position
1308 vector sb_heading_fontsize;
1309 sb_heading_fontsize = hud_fontsize * 2;
1310 draw_beginBoldFont();
1311 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1314 pos.y += sb_heading_fontsize.y;
1315 pos.y += panel_bg_border;
1317 // Draw the scoreboard
1318 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * ((autocvar_scoreboard_bg_scale > 0) ? autocvar_scoreboard_bg_scale : 0.25);
1322 vector team_score_baseoffset = eY * hud_fontsize.y - eX * (panel_bg_border + hud_fontsize.x * 0.5);
1323 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1325 if(tm.team == NUM_SPECTATOR)
1327 if(!tm.team && teamplay)
1330 draw_beginBoldFont();
1331 vector rgb = Team_ColorRGB(tm.team);
1332 str = ftos(tm.(teamscores[ts_primary]));
1333 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1335 if(ts_primary != ts_secondary)
1337 str = ftos(tm.(teamscores[ts_secondary]));
1338 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);
1342 panel_bg_color = rgb * panel_bg_color_team;
1343 pos = HUD_Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1345 panel_bg_color = Team_ColorRGB(myteam) * panel_bg_color_team;
1349 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1351 if(tm.team == NUM_SPECTATOR)
1353 if(!tm.team && teamplay)
1356 pos = HUD_Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1360 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1361 if(race_speedaward) {
1362 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);
1363 pos.y += 1.25 * hud_fontsize.y;
1365 if(race_speedaward_alltimebest) {
1366 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);
1367 pos.y += 1.25 * hud_fontsize.y;
1369 pos = HUD_DrawScoreboardRankings(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1371 else if (autocvar_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL) {
1372 pos = HUD_DrawScoreboardAccuracyStats(pos, panel_bg_color, bg_size);
1375 pos = HUD_DrawMapStats(pos, panel_bg_color, bg_size);
1380 for(pl = players.sort_next; pl; pl = pl.sort_next)
1382 if(pl.team != NUM_SPECTATOR)
1384 pos.y += 1.25 * hud_fontsize.y;
1385 HUD_PrintScoreboardItem(pos, panel_bg_color, pl, (pl.sv_entnum == player_localnum), specs);
1391 draw_beginBoldFont();
1392 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1394 pos.y += 1.25 * hud_fontsize.y;
1397 // Print info string
1399 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1400 tl = STAT(TIMELIMIT);
1401 fl = STAT(FRAGLIMIT);
1402 ll = STAT(LEADLIMIT);
1403 if(gametype == MAPINFO_TYPE_LMS)
1406 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1411 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1415 str = strcat(str, _(" or"));
1418 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], fl),
1419 (teamscores_label[ts_primary] == "score") ? CTX(_("SCO^points")) :
1420 (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1421 TranslateScoresLabel(teamscores_label[ts_primary])));
1425 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags[ps_primary], fl),
1426 (scores_label[ps_primary] == "score") ? CTX(_("SCO^points")) :
1427 (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1428 TranslateScoresLabel(scores_label[ps_primary])));
1433 if(tl > 0 || fl > 0)
1434 str = strcat(str, _(" or"));
1437 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], ll),
1438 (teamscores_label[ts_primary] == "score") ? CTX(_("SCO^points")) :
1439 (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1440 TranslateScoresLabel(teamscores_label[ts_primary])));
1444 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags[ps_primary], ll),
1445 (scores_label[ps_primary] == "score") ? CTX(_("SCO^points")) :
1446 (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1447 TranslateScoresLabel(scores_label[ps_primary])));
1452 pos.y += 1.2 * hud_fontsize.y;
1453 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1455 // print information about respawn status
1456 float respawn_time = STAT(RESPAWN_TIME);
1460 if(respawn_time < 0)
1462 // a negative number means we are awaiting respawn, time value is still the same
1463 respawn_time *= -1; // remove mark now that we checked it
1464 respawn_time = max(time, respawn_time); // don't show a negative value while the server is respawning the player (lag)
1466 str = sprintf(_("^1Respawning in ^3%s^1..."),
1467 (autocvar_scoreboard_respawntime_decimals ?
1468 count_seconds_decs(respawn_time - time, autocvar_scoreboard_respawntime_decimals)
1470 count_seconds(respawn_time - time)
1474 else if(time < respawn_time)
1476 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1477 (autocvar_scoreboard_respawntime_decimals ?
1478 count_seconds_decs(respawn_time - time, autocvar_scoreboard_respawntime_decimals)
1480 count_seconds(respawn_time - time)
1484 else if(time >= respawn_time)
1485 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1487 pos.y += 1.2 * hud_fontsize.y;
1488 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1491 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;