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);
1023 // column highlighting
1024 for (int i = 0; i < columnns; ++i)
1026 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1029 for (int i = 0; i < rows; ++i)
1030 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1032 average_accuracy = 0;
1033 int weapons_with_stats = 0;
1035 pos.x += weapon_width / 2;
1037 if (autocvar_scoreboard_accuracy_nocolors)
1040 Accuracy_LoadColors();
1042 float oldposx = pos.x;
1046 FOREACH(Weapons, it != WEP_Null, {
1047 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1049 WepSet set = it.m_wepset;
1050 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1054 if (weapon_stats >= 0)
1055 weapon_alpha = sbt_fg_alpha;
1057 weapon_alpha = 0.2 * sbt_fg_alpha;
1060 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1062 if (weapon_stats >= 0) {
1063 weapons_with_stats += 1;
1064 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1067 s = sprintf("%d%%", weapon_stats * 100);
1070 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1072 if(!autocvar_scoreboard_accuracy_nocolors)
1073 rgb = Accuracy_GetColor(weapon_stats);
1075 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1077 tmpos.x += weapon_width * rows;
1078 pos.x += weapon_width * rows;
1079 if (rows == 2 && column == columnns - 1) {
1087 if (weapons_with_stats)
1088 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1090 panel_size.x += panel_bg_padding * 2; // restore initial width
1094 vector HUD_DrawKeyValue(vector pos, string key, string value) {
1096 pos.x += hud_fontsize.x * 0.25;
1097 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1098 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1099 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1101 pos.y += hud_fontsize.y;
1106 vector HUD_DrawMapStats(vector pos, vector rgb, vector bg_size) {
1107 float stat_secrets_found, stat_secrets_total;
1108 float stat_monsters_killed, stat_monsters_total;
1112 // get monster stats
1113 stat_monsters_killed = STAT(MONSTERS_KILLED);
1114 stat_monsters_total = STAT(MONSTERS_TOTAL);
1116 // get secrets stats
1117 stat_secrets_found = STAT(SECRETS_FOUND);
1118 stat_secrets_total = STAT(SECRETS_TOTAL);
1120 // get number of rows
1121 if(stat_secrets_total)
1123 if(stat_monsters_total)
1126 // if no rows, return
1130 // draw table header
1131 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1132 pos.y += 1.25 * hud_fontsize.y;
1133 pos.y += panel_bg_border;
1136 panel_size.y = hud_fontsize.y * rows;
1137 panel_size.y += panel_bg_padding * 2;
1138 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1140 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1142 if(panel_bg_padding)
1144 panel_pos += '1 1 0' * panel_bg_padding;
1145 panel_size -= '2 2 0' * panel_bg_padding;
1149 vector tmp = panel_size;
1152 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1155 if(stat_monsters_total)
1157 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1158 pos = HUD_DrawKeyValue(pos, _("Monsters killed:"), val);
1162 if(stat_secrets_total)
1164 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1165 pos = HUD_DrawKeyValue(pos, _("Secrets found:"), val);
1168 panel_size.x += panel_bg_padding * 2; // restore initial width
1173 vector HUD_DrawScoreboardRankings(vector pos, entity pl, vector rgb, vector bg_size)
1176 RANKINGS_RECEIVED_CNT = 0;
1177 for (i=RANKINGS_CNT-1; i>=0; --i)
1179 ++RANKINGS_RECEIVED_CNT;
1181 if (RANKINGS_RECEIVED_CNT == 0)
1184 vector hl_rgb = rgb + '0.5 0.5 0.5';
1186 pos.y += hud_fontsize.y;
1187 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1188 pos.y += 1.25 * hud_fontsize.y;
1189 pos.y += panel_bg_border;
1192 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1193 panel_size.y += panel_bg_padding * 2;
1194 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1196 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1198 if(panel_bg_padding)
1200 panel_pos += '1 1 0' * panel_bg_padding;
1201 panel_size -= '2 2 0' * panel_bg_padding;
1205 vector tmp = panel_size;
1208 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1211 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1218 n = grecordholder[i];
1219 p = count_ordinal(i+1);
1220 if(grecordholder[i] == entcs_GetName(player_localnum))
1221 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1222 else if(!(i % 2) && sbt_highlight)
1223 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1224 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1225 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);
1226 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1227 pos.y += 1.25 * hud_fontsize.y;
1230 panel_size.x += panel_bg_padding * 2; // restore initial width
1234 float hud_woulddrawscoreboard_prev;
1235 float hud_woulddrawscoreboard_change; // "time" at which HUD_WouldDrawScoreboard() changed
1236 void HUD_DrawScoreboard()
1238 if(!autocvar__hud_configure)
1240 float hud_woulddrawscoreboard;
1241 hud_woulddrawscoreboard = scoreboard_active;
1242 if(hud_woulddrawscoreboard != hud_woulddrawscoreboard_prev) {
1243 hud_woulddrawscoreboard_change = time;
1244 hud_woulddrawscoreboard_prev = hud_woulddrawscoreboard;
1247 if(hud_woulddrawscoreboard) {
1248 if(menu_enabled == 1)
1249 scoreboard_fade_alpha = 1;
1250 float scoreboard_fadeinspeed = autocvar_scoreboard_fadeinspeed;
1251 if (scoreboard_fadeinspeed)
1252 scoreboard_fade_alpha = bound (0, (time - hud_woulddrawscoreboard_change) * scoreboard_fadeinspeed, 1);
1254 scoreboard_fade_alpha = 1;
1257 float scoreboard_fadeoutspeed = autocvar_scoreboard_fadeoutspeed;
1258 if (scoreboard_fadeoutspeed)
1259 scoreboard_fade_alpha = bound (0, (1/scoreboard_fadeoutspeed - (time - hud_woulddrawscoreboard_change)) * scoreboard_fadeoutspeed, 1);
1261 scoreboard_fade_alpha = 0;
1264 if (!scoreboard_fade_alpha)
1268 scoreboard_fade_alpha = 0;
1270 if (autocvar_scoreboard_dynamichud)
1273 HUD_Scale_Disable();
1275 float hud_fade_alpha_save = hud_fade_alpha;
1276 if(menu_enabled == 1)
1279 hud_fade_alpha = scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1280 HUD_Panel_UpdateCvars();
1282 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1283 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1284 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1285 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1286 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1287 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1289 hud_fade_alpha = hud_fade_alpha_save;
1291 // don't overlap with con_notify
1292 if(!autocvar__hud_configure)
1293 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1295 Scoreboard_UpdatePlayerTeams();
1301 // Initializes position
1305 vector sb_heading_fontsize;
1306 sb_heading_fontsize = hud_fontsize * 2;
1307 draw_beginBoldFont();
1308 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1311 pos.y += sb_heading_fontsize.y;
1312 pos.y += panel_bg_border;
1314 // Draw the scoreboard
1315 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * ((autocvar_scoreboard_bg_scale > 0) ? autocvar_scoreboard_bg_scale : 0.25);
1319 vector team_score_baseoffset = eY * hud_fontsize.y - eX * (panel_bg_border + hud_fontsize.x * 0.5);
1320 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1322 if(tm.team == NUM_SPECTATOR)
1324 if(!tm.team && teamplay)
1327 draw_beginBoldFont();
1328 vector rgb = Team_ColorRGB(tm.team);
1329 str = ftos(tm.(teamscores[ts_primary]));
1330 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1332 if(ts_primary != ts_secondary)
1334 str = ftos(tm.(teamscores[ts_secondary]));
1335 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);
1339 panel_bg_color = rgb * panel_bg_color_team;
1340 pos = HUD_Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1342 panel_bg_color = Team_ColorRGB(myteam) * panel_bg_color_team;
1346 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1348 if(tm.team == NUM_SPECTATOR)
1350 if(!tm.team && teamplay)
1353 pos = HUD_Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1357 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1358 if(race_speedaward) {
1359 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);
1360 pos.y += 1.25 * hud_fontsize.y;
1362 if(race_speedaward_alltimebest) {
1363 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);
1364 pos.y += 1.25 * hud_fontsize.y;
1366 pos = HUD_DrawScoreboardRankings(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1368 else if (autocvar_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL) {
1369 pos = HUD_DrawScoreboardAccuracyStats(pos, panel_bg_color, bg_size);
1372 pos = HUD_DrawMapStats(pos, panel_bg_color, bg_size);
1377 for(pl = players.sort_next; pl; pl = pl.sort_next)
1379 if(pl.team != NUM_SPECTATOR)
1381 pos.y += 1.25 * hud_fontsize.y;
1382 HUD_PrintScoreboardItem(pos, panel_bg_color, pl, (pl.sv_entnum == player_localnum), specs);
1388 draw_beginBoldFont();
1389 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1391 pos.y += 1.25 * hud_fontsize.y;
1394 // Print info string
1396 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1397 tl = STAT(TIMELIMIT);
1398 fl = STAT(FRAGLIMIT);
1399 ll = STAT(LEADLIMIT);
1400 if(gametype == MAPINFO_TYPE_LMS)
1403 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1408 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1412 str = strcat(str, _(" or"));
1415 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], fl),
1416 (teamscores_label[ts_primary] == "score") ? CTX(_("SCO^points")) :
1417 (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1418 TranslateScoresLabel(teamscores_label[ts_primary])));
1422 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags[ps_primary], fl),
1423 (scores_label[ps_primary] == "score") ? CTX(_("SCO^points")) :
1424 (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1425 TranslateScoresLabel(scores_label[ps_primary])));
1430 if(tl > 0 || fl > 0)
1431 str = strcat(str, _(" or"));
1434 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], ll),
1435 (teamscores_label[ts_primary] == "score") ? CTX(_("SCO^points")) :
1436 (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1437 TranslateScoresLabel(teamscores_label[ts_primary])));
1441 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags[ps_primary], ll),
1442 (scores_label[ps_primary] == "score") ? CTX(_("SCO^points")) :
1443 (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1444 TranslateScoresLabel(scores_label[ps_primary])));
1449 pos.y += 1.2 * hud_fontsize.y;
1450 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1452 // print information about respawn status
1453 float respawn_time = STAT(RESPAWN_TIME);
1457 if(respawn_time < 0)
1459 // a negative number means we are awaiting respawn, time value is still the same
1460 respawn_time *= -1; // remove mark now that we checked it
1461 respawn_time = max(time, respawn_time); // don't show a negative value while the server is respawning the player (lag)
1463 str = sprintf(_("^1Respawning in ^3%s^1..."),
1464 (autocvar_scoreboard_respawntime_decimals ?
1465 count_seconds_decs(respawn_time - time, autocvar_scoreboard_respawntime_decimals)
1467 count_seconds(respawn_time - time)
1471 else if(time < respawn_time)
1473 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
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)
1482 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1484 pos.y += 1.2 * hud_fontsize.y;
1485 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1488 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;