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