1 #include "scoreboard.qh"
3 #include <client/draw.qh>
4 #include <client/hud/panel/chat.qh>
5 #include <client/hud/panel/quickmenu.qh>
6 #include <client/hud/panel/racetimer.qh>
7 #include <client/hud/panel/weapons.qh>
8 #include <common/constants.qh>
9 #include <common/ent_cs.qh>
10 #include <common/mapinfo.qh>
11 #include <common/minigames/cl_minigames.qh>
12 #include <common/net_linked.qh>
13 #include <common/scores.qh>
14 #include <common/stats.qh>
15 #include <common/teams.qh>
16 #include <common/items/inventory.qh>
20 void Scoreboard_Draw_Export(int fh)
22 // allow saving cvars that aesthetically change the panel into hud skin files
23 HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
24 HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
25 HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
26 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
27 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
28 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
29 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
30 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
31 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
32 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
33 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_eliminated");
34 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
35 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
36 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
39 const int MAX_SBT_FIELDS = MAX_SCORE;
41 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
42 float sbt_field_size[MAX_SBT_FIELDS + 1];
43 string sbt_field_title[MAX_SBT_FIELDS + 1];
46 string autocvar_hud_fontsize;
47 string hud_fontsize_str;
52 float sbt_fg_alpha_self;
54 float sbt_highlight_alpha;
55 float sbt_highlight_alpha_self;
56 float sbt_highlight_alpha_eliminated;
58 // provide basic panel cvars to old clients
59 // TODO remove them after a future release (0.8.2+)
60 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
61 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
62 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
63 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
64 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
65 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
66 noref string autocvar_hud_panel_scoreboard_bg_border = "";
67 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
69 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
70 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
71 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
72 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
73 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
74 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
75 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
76 bool autocvar_hud_panel_scoreboard_table_highlight = true;
77 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
78 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
79 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
80 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
81 float autocvar_hud_panel_scoreboard_namesize = 15;
82 float autocvar_hud_panel_scoreboard_team_size_position = 0;
84 bool autocvar_hud_panel_scoreboard_accuracy = true;
85 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
86 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
87 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
88 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
90 bool autocvar_hud_panel_scoreboard_itemstats = true;
91 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
92 bool autocvar_hud_panel_scoreboard_itemstats_filter = true;
93 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
94 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
96 bool autocvar_hud_panel_scoreboard_dynamichud = false;
98 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
99 bool autocvar_hud_panel_scoreboard_others_showscore = true;
100 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
101 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
102 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
103 bool autocvar_hud_panel_scoreboard_playerid = false;
104 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
105 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
107 // mode 0: returns translated label
108 // mode 1: prints name and description of all the labels
109 string Label_getInfo(string label, int mode)
112 label = "bckills"; // first case in the switch
116 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
117 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
118 case "caps": if (!mode) return CTX(_("SCO^caps")); else LOG_HELP(strcat("^3", "caps", " ^7", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
119 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
120 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
121 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
122 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
123 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
124 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
125 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
126 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
127 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
128 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
129 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
130 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
131 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
132 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
133 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
134 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
135 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
136 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
137 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
138 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
139 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
140 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
141 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
142 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
143 case "pickups": if (!mode) return CTX(_("SCO^pickups")); else LOG_HELP(strcat("^3", "pickups", " ^7", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
144 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
145 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
146 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
147 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
148 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
149 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
150 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
151 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
152 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
153 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
154 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
155 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
156 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
157 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
158 default: return label;
163 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
164 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
166 #define SB_EXTRA_SORTING_FIELDS 5
167 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
168 void Scoreboard_InitScores()
172 ps_primary = ps_secondary = NULL;
173 ts_primary = ts_secondary = -1;
174 FOREACH(Scores, true, {
175 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
176 if(f == SFL_SORT_PRIO_PRIMARY)
178 if(f == SFL_SORT_PRIO_SECONDARY)
180 if(ps_primary == it || ps_secondary == it)
182 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
183 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
184 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
185 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
186 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
188 if(ps_secondary == NULL)
189 ps_secondary = ps_primary;
191 for(i = 0; i < MAX_TEAMSCORE; ++i)
193 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
194 if(f == SFL_SORT_PRIO_PRIMARY)
196 if(f == SFL_SORT_PRIO_SECONDARY)
199 if(ts_secondary == -1)
200 ts_secondary = ts_primary;
202 Cmd_Scoreboard_SetFields(0);
206 void Scoreboard_UpdatePlayerTeams()
210 for(pl = players.sort_next; pl; pl = pl.sort_next)
213 int Team = entcs_GetScoreTeam(pl.sv_entnum);
214 if(SetTeam(pl, Team))
217 Scoreboard_UpdatePlayerPos(pl);
221 pl = players.sort_next;
226 print(strcat("PNUM: ", ftos(num), "\n"));
231 int Scoreboard_CompareScore(int vl, int vr, int f)
233 TC(int, vl); TC(int, vr); TC(int, f);
234 if(f & SFL_ZERO_IS_WORST)
236 if(vl == 0 && vr != 0)
238 if(vl != 0 && vr == 0)
242 return IS_INCREASING(f);
244 return IS_DECREASING(f);
248 float Scoreboard_ComparePlayerScores(entity left, entity right)
250 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
251 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
258 if(vl == NUM_SPECTATOR)
260 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
262 if(!left.gotscores && right.gotscores)
269 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
273 if (!fld) fld = ps_primary;
274 else if (ps_secondary == ps_primary) continue;
275 else fld = ps_secondary;
279 fld = sb_extra_sorting_field[i];
280 if (fld == ps_primary || fld == ps_secondary) continue;
284 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
285 if (r >= 0) return r;
288 if (left.sv_entnum < right.sv_entnum)
294 void Scoreboard_UpdatePlayerPos(entity player)
297 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
299 SORT_SWAP(player, ent);
301 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
303 SORT_SWAP(ent, player);
307 float Scoreboard_CompareTeamScores(entity left, entity right)
309 if(left.team == NUM_SPECTATOR)
311 if(right.team == NUM_SPECTATOR)
316 for(int i = -2; i < MAX_TEAMSCORE; ++i)
320 if (fld_idx == -1) fld_idx = ts_primary;
321 else if (ts_secondary == ts_primary) continue;
322 else fld_idx = ts_secondary;
327 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
330 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
331 if (r >= 0) return r;
334 if (left.team < right.team)
340 void Scoreboard_UpdateTeamPos(entity Team)
343 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
345 SORT_SWAP(Team, ent);
347 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
349 SORT_SWAP(ent, Team);
353 void Cmd_Scoreboard_Help()
355 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
356 LOG_HELP(_("Usage:"));
357 LOG_HELP("^2scoreboard_columns_set ^3default");
358 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
359 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
360 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
361 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
362 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
363 LOG_HELP(_("The following field names are recognized (case insensitive):"));
369 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
370 "of game types, then a slash, to make the field show up only in these\n"
371 "or in all but these game types. You can also specify 'all' as a\n"
372 "field to show all fields available for the current game mode."));
375 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
376 "include/exclude ALL teams/noteams game modes."));
379 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
380 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
381 "right of the vertical bar aligned to the right."));
382 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
383 "other gamemodes except DM."));
386 // NOTE: adding a gametype with ? to not warn for an optional field
387 // make sure it's excluded in a previous exclusive rule, if any
388 // otherwise the previous exclusive rule warns anyway
389 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
390 #define SCOREBOARD_DEFAULT_COLUMNS \
391 "ping pl fps name |" \
392 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
393 " -teams,lms/deaths +ft,tdm/deaths" \
395 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
396 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
397 " +tdm,ft,dom,ons,as/teamkills"\
398 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
399 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
400 " +lms/lives +lms/rank" \
401 " +kh/kckills +kh/losses +kh/caps" \
402 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
403 " +as/objectives +nb/faults +nb/goals" \
404 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
405 " +dom/ticks +dom/takes" \
406 " -lms,rc,cts,inv,nb/score"
408 void Cmd_Scoreboard_SetFields(int argc)
413 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
417 return; // do nothing, we don't know gametype and scores yet
419 // sbt_fields uses strunzone on the titles!
420 if(!sbt_field_title[0])
421 for(i = 0; i < MAX_SBT_FIELDS; ++i)
422 sbt_field_title[i] = strzone("(null)");
424 // TODO: re enable with gametype dependant cvars?
425 if(argc < 3) // no arguments provided
426 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
429 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
433 if(argv(2) == "default" || argv(2) == "expand_default")
435 if(argv(2) == "expand_default")
436 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
437 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
439 else if(argv(2) == "all" || argv(2) == "ALL")
441 string s = "ping pl name |"; // scores without label (not really scores)
444 // scores without label
445 s = strcat(s, " ", "sum");
446 s = strcat(s, " ", "kdratio");
447 s = strcat(s, " ", "frags");
449 FOREACH(Scores, true, {
451 if(it != ps_secondary)
452 if(scores_label(it) != "")
453 s = strcat(s, " ", scores_label(it));
455 if(ps_secondary != ps_primary)
456 s = strcat(s, " ", scores_label(ps_secondary));
457 s = strcat(s, " ", scores_label(ps_primary));
458 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
465 hud_fontsize = HUD_GetFontsize("hud_fontsize");
467 for(i = 1; i < argc - 1; ++i)
470 bool nocomplain = false;
471 if(substring(str, 0, 1) == "?")
474 str = substring(str, 1, strlen(str) - 1);
477 slash = strstrofs(str, "/", 0);
480 pattern = substring(str, 0, slash);
481 str = substring(str, slash + 1, strlen(str) - (slash + 1));
483 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
487 str = strtolower(str);
488 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
489 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
494 // fields without a label (not networked via the score system)
495 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
496 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
497 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
498 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
499 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
500 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
501 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
502 default: // fields with a label
504 // map alternative labels
505 if (str == "damage") str = "dmg";
506 if (str == "damagetaken") str = "dmgtaken";
508 FOREACH(Scores, true, {
509 if (str == strtolower(scores_label(it))) {
511 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
515 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
516 if(!nocomplain && str != "fps") // server can disable the fps field
517 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
519 strfree(sbt_field_title[sbt_num_fields]);
520 sbt_field_size[sbt_num_fields] = 0;
524 sbt_field[sbt_num_fields] = j;
527 if(j == ps_secondary)
528 have_secondary = true;
533 if(sbt_num_fields >= MAX_SBT_FIELDS)
537 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
539 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
540 have_secondary = true;
541 if(ps_primary == ps_secondary)
542 have_secondary = true;
543 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
545 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
549 strfree(sbt_field_title[sbt_num_fields]);
550 for(i = sbt_num_fields; i > 0; --i)
552 sbt_field_title[i] = sbt_field_title[i-1];
553 sbt_field_size[i] = sbt_field_size[i-1];
554 sbt_field[i] = sbt_field[i-1];
556 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
557 sbt_field[0] = SP_NAME;
559 LOG_INFO("fixed missing field 'name'");
563 strfree(sbt_field_title[sbt_num_fields]);
564 for(i = sbt_num_fields; i > 1; --i)
566 sbt_field_title[i] = sbt_field_title[i-1];
567 sbt_field_size[i] = sbt_field_size[i-1];
568 sbt_field[i] = sbt_field[i-1];
570 sbt_field_title[1] = strzone("|");
571 sbt_field[1] = SP_SEPARATOR;
572 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
574 LOG_INFO("fixed missing field '|'");
577 else if(!have_separator)
579 strcpy(sbt_field_title[sbt_num_fields], "|");
580 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
581 sbt_field[sbt_num_fields] = SP_SEPARATOR;
583 LOG_INFO("fixed missing field '|'");
587 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
588 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
589 sbt_field[sbt_num_fields] = ps_secondary;
591 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
595 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
596 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
597 sbt_field[sbt_num_fields] = ps_primary;
599 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
603 sbt_field[sbt_num_fields] = SP_END;
606 string Scoreboard_AddPlayerId(string pl_name, entity pl)
608 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
609 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
610 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
614 vector sbt_field_rgb;
615 string sbt_field_icon0;
616 string sbt_field_icon1;
617 string sbt_field_icon2;
618 vector sbt_field_icon0_rgb;
619 vector sbt_field_icon1_rgb;
620 vector sbt_field_icon2_rgb;
621 string Scoreboard_GetName(entity pl)
623 if(ready_waiting && pl.ready)
625 sbt_field_icon0 = "gfx/scoreboard/player_ready";
629 int f = entcs_GetClientColors(pl.sv_entnum);
631 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
632 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
633 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
634 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
635 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
638 return entcs_GetName(pl.sv_entnum);
641 string Scoreboard_GetField(entity pl, PlayerScoreField field)
643 float tmp, num, denom;
646 sbt_field_rgb = '1 1 1';
647 sbt_field_icon0 = "";
648 sbt_field_icon1 = "";
649 sbt_field_icon2 = "";
650 sbt_field_icon0_rgb = '1 1 1';
651 sbt_field_icon1_rgb = '1 1 1';
652 sbt_field_icon2_rgb = '1 1 1';
657 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
658 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
662 tmp = max(0, min(220, f-80)) / 220;
663 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
669 f = pl.ping_packetloss;
670 tmp = pl.ping_movementloss;
671 if(f == 0 && tmp == 0)
673 str = ftos(ceil(f * 100));
675 str = strcat(str, "~", ftos(ceil(tmp * 100)));
676 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
677 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
681 str = Scoreboard_GetName(pl);
682 if (autocvar_hud_panel_scoreboard_playerid)
683 str = Scoreboard_AddPlayerId(str, pl);
687 f = pl.(scores(SP_KILLS));
688 f -= pl.(scores(SP_SUICIDES));
692 num = pl.(scores(SP_KILLS));
693 denom = pl.(scores(SP_DEATHS));
696 sbt_field_rgb = '0 1 0';
697 str = sprintf("%d", num);
698 } else if(num <= 0) {
699 sbt_field_rgb = '1 0 0';
700 str = sprintf("%.1f", num/denom);
702 str = sprintf("%.1f", num/denom);
706 f = pl.(scores(SP_KILLS));
707 f -= pl.(scores(SP_DEATHS));
710 sbt_field_rgb = '0 1 0';
712 sbt_field_rgb = '1 1 1';
714 sbt_field_rgb = '1 0 0';
720 float elo = pl.(scores(SP_ELO));
722 case -1: return "...";
723 case -2: return _("N/A");
724 default: return ftos(elo);
730 float fps = pl.(scores(SP_FPS));
733 sbt_field_rgb = '1 1 1';
734 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
736 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
737 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
741 case SP_DMG: case SP_DMGTAKEN:
742 return sprintf("%.1f k", pl.(scores(field)) / 1000);
744 default: case SP_SCORE:
745 tmp = pl.(scores(field));
746 f = scores_flags(field);
747 if(field == ps_primary)
748 sbt_field_rgb = '1 1 0';
749 else if(field == ps_secondary)
750 sbt_field_rgb = '0 1 1';
752 sbt_field_rgb = '1 1 1';
753 return ScoreString(f, tmp);
758 float sbt_fixcolumnwidth_len;
759 float sbt_fixcolumnwidth_iconlen;
760 float sbt_fixcolumnwidth_marginlen;
762 string Scoreboard_FixColumnWidth(int i, string str)
768 sbt_fixcolumnwidth_iconlen = 0;
770 if(sbt_field_icon0 != "")
772 sz = draw_getimagesize(sbt_field_icon0);
774 if(sbt_fixcolumnwidth_iconlen < f)
775 sbt_fixcolumnwidth_iconlen = f;
778 if(sbt_field_icon1 != "")
780 sz = draw_getimagesize(sbt_field_icon1);
782 if(sbt_fixcolumnwidth_iconlen < f)
783 sbt_fixcolumnwidth_iconlen = f;
786 if(sbt_field_icon2 != "")
788 sz = draw_getimagesize(sbt_field_icon2);
790 if(sbt_fixcolumnwidth_iconlen < f)
791 sbt_fixcolumnwidth_iconlen = f;
794 if(sbt_fixcolumnwidth_iconlen != 0)
796 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
797 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
800 sbt_fixcolumnwidth_marginlen = 0;
802 if(sbt_field[i] == SP_NAME) // name gets all remaining space
805 float remaining_space = 0;
806 for(j = 0; j < sbt_num_fields; ++j)
808 if (sbt_field[i] != SP_SEPARATOR)
809 remaining_space += sbt_field_size[j] + hud_fontsize.x;
810 sbt_field_size[i] = panel_size.x - remaining_space;
812 if (sbt_fixcolumnwidth_iconlen != 0)
813 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
814 float namesize = panel_size.x - remaining_space;
815 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
816 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
818 max_namesize = vid_conwidth - remaining_space;
821 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
823 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
824 if(sbt_field_size[i] < f)
825 sbt_field_size[i] = f;
830 void Scoreboard_initFieldSizes()
832 for(int i = 0; i < sbt_num_fields; ++i)
834 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
835 Scoreboard_FixColumnWidth(i, "");
839 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
842 vector column_dim = eY * panel_size.y;
844 column_dim.y -= 1.25 * hud_fontsize.y;
845 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
846 pos.x += hud_fontsize.x * 0.5;
847 for(i = 0; i < sbt_num_fields; ++i)
849 if(sbt_field[i] == SP_SEPARATOR)
851 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
854 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
855 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
856 pos.x += column_dim.x;
858 if(sbt_field[i] == SP_SEPARATOR)
860 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
861 for(i = sbt_num_fields - 1; i > 0; --i)
863 if(sbt_field[i] == SP_SEPARATOR)
866 pos.x -= sbt_field_size[i];
871 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
872 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
875 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
876 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
877 pos.x -= hud_fontsize.x;
882 pos.y += 1.25 * hud_fontsize.y;
886 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
888 TC(bool, is_self); TC(int, pl_number);
890 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
892 vector h_pos = item_pos;
893 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
894 // alternated rows highlighting
896 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
897 else if((sbt_highlight) && (!(pl_number % 2)))
898 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
900 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
902 vector pos = item_pos;
903 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
905 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
907 pos.x += hud_fontsize.x * 0.5;
908 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
909 vector tmp = '0 0 0';
911 PlayerScoreField field;
912 for(i = 0; i < sbt_num_fields; ++i)
914 field = sbt_field[i];
915 if(field == SP_SEPARATOR)
918 if(is_spec && field != SP_NAME && field != SP_PING) {
919 pos.x += sbt_field_size[i] + hud_fontsize.x;
922 str = Scoreboard_GetField(pl, field);
923 str = Scoreboard_FixColumnWidth(i, str);
925 pos.x += sbt_field_size[i] + hud_fontsize.x;
927 if(field == SP_NAME) {
928 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
929 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
931 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
932 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
935 tmp.x = sbt_field_size[i] + hud_fontsize.x;
936 if(sbt_field_icon0 != "")
937 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
938 if(sbt_field_icon1 != "")
939 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
940 if(sbt_field_icon2 != "")
941 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
944 if(sbt_field[i] == SP_SEPARATOR)
946 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
947 for(i = sbt_num_fields-1; i > 0; --i)
949 field = sbt_field[i];
950 if(field == SP_SEPARATOR)
953 if(is_spec && field != SP_NAME && field != SP_PING) {
954 pos.x -= sbt_field_size[i] + hud_fontsize.x;
958 str = Scoreboard_GetField(pl, field);
959 str = Scoreboard_FixColumnWidth(i, str);
961 if(field == SP_NAME) {
962 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
963 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
965 tmp.x = sbt_fixcolumnwidth_len;
966 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
969 tmp.x = sbt_field_size[i];
970 if(sbt_field_icon0 != "")
971 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
972 if(sbt_field_icon1 != "")
973 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
974 if(sbt_field_icon2 != "")
975 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
976 pos.x -= sbt_field_size[i] + hud_fontsize.x;
981 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
984 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
987 vector h_pos = item_pos;
988 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
990 bool complete = (this_team == NUM_SPECTATOR);
993 if((sbt_highlight) && (!(pl_number % 2)))
994 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
996 vector pos = item_pos;
997 pos.x += hud_fontsize.x * 0.5;
998 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1000 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1002 width_limit -= stringwidth("...", false, hud_fontsize);
1003 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1004 static float max_name_width = 0;
1006 float fieldsize = 0;
1007 float min_fieldsize = 0;
1008 float fieldpadding = hud_fontsize.x * 0.25;
1009 if(this_team == NUM_SPECTATOR)
1011 if(autocvar_hud_panel_scoreboard_spectators_showping)
1012 min_fieldsize = stringwidth("999", false, hud_fontsize);
1014 else if(autocvar_hud_panel_scoreboard_others_showscore)
1015 min_fieldsize = stringwidth("99", false, hud_fontsize);
1016 for(i = 0; pl; pl = pl.sort_next)
1018 if(pl.team != this_team)
1020 if(pl == ignored_pl)
1024 if(this_team == NUM_SPECTATOR)
1026 if(autocvar_hud_panel_scoreboard_spectators_showping)
1027 field = Scoreboard_GetField(pl, SP_PING);
1029 else if(autocvar_hud_panel_scoreboard_others_showscore)
1030 field = Scoreboard_GetField(pl, SP_SCORE);
1032 string str = entcs_GetName(pl.sv_entnum);
1033 if (autocvar_hud_panel_scoreboard_playerid)
1034 str = Scoreboard_AddPlayerId(str, pl);
1035 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1036 float column_width = stringwidth(str, true, hud_fontsize);
1037 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1039 if(column_width > max_name_width)
1040 max_name_width = column_width;
1041 column_width = max_name_width;
1045 fieldsize = stringwidth(field, false, hud_fontsize);
1046 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1049 if(pos.x + column_width > width_limit)
1054 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1059 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1060 pos.y += hud_fontsize.y * 1.25;
1064 vector name_pos = pos;
1065 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1066 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1067 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1070 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1071 h_size.y = hud_fontsize.y;
1072 vector field_pos = pos;
1073 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1074 field_pos.x += column_width - h_size.x;
1076 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1077 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1078 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1082 h_size.x = column_width + hud_fontsize.x * 0.25;
1083 h_size.y = hud_fontsize.y;
1084 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1086 pos.x += column_width;
1087 pos.x += hud_fontsize.x;
1089 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1092 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1094 int max_players = 999;
1095 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1097 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1100 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1101 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1102 height /= team_count;
1105 height -= panel_bg_padding * 2; // - padding
1106 max_players = floor(height / (hud_fontsize.y * 1.25));
1107 if(max_players <= 1)
1109 if(max_players == tm.team_size)
1114 entity me = playerslots[current_player];
1116 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1117 panel_size.y += panel_bg_padding * 2;
1120 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1121 if(panel.current_panel_bg != "0")
1122 end_pos.y += panel_bg_border * 2;
1124 if(panel_bg_padding)
1126 panel_pos += '1 1 0' * panel_bg_padding;
1127 panel_size -= '2 2 0' * panel_bg_padding;
1131 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1135 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1137 pos.y += 1.25 * hud_fontsize.y;
1140 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1142 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1145 // print header row and highlight columns
1146 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1148 // fill the table and draw the rows
1149 bool is_self = false;
1150 bool self_shown = false;
1152 for(pl = players.sort_next; pl; pl = pl.sort_next)
1154 if(pl.team != tm.team)
1156 if(i == max_players - 2 && pl != me)
1158 if(!self_shown && me.team == tm.team)
1160 Scoreboard_DrawItem(pos, rgb, me, true, i);
1162 pos.y += 1.25 * hud_fontsize.y;
1166 if(i >= max_players - 1)
1168 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1171 is_self = (pl.sv_entnum == current_player);
1172 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1175 pos.y += 1.25 * hud_fontsize.y;
1179 panel_size.x += panel_bg_padding * 2; // restore initial width
1183 bool Scoreboard_WouldDraw()
1185 if (MUTATOR_CALLHOOK(DrawScoreboard))
1187 else if (QuickMenu_IsOpened())
1189 else if (HUD_Radar_Clickable())
1191 else if (scoreboard_showscores)
1193 else if (intermission == 1)
1195 else if (intermission == 2)
1197 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1198 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1202 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1207 float average_accuracy;
1208 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1210 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1212 WepSet weapons_stat = WepSet_GetFromStat();
1213 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1214 int disownedcnt = 0;
1216 FOREACH(Weapons, it != WEP_Null, {
1217 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1219 WepSet set = it.m_wepset;
1220 if(it.spawnflags & WEP_TYPE_OTHER)
1225 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1227 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1234 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1235 if (weapon_cnt <= 0) return pos;
1238 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1240 int columnns = ceil(weapon_cnt / rows);
1242 float weapon_height = 29;
1243 float height = hud_fontsize.y + weapon_height;
1245 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1246 pos.y += 1.25 * hud_fontsize.y;
1247 if(panel.current_panel_bg != "0")
1248 pos.y += panel_bg_border;
1251 panel_size.y = height * rows;
1252 panel_size.y += panel_bg_padding * 2;
1254 float panel_bg_alpha_save = panel_bg_alpha;
1255 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1257 panel_bg_alpha = panel_bg_alpha_save;
1259 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1260 if(panel.current_panel_bg != "0")
1261 end_pos.y += panel_bg_border * 2;
1263 if(panel_bg_padding)
1265 panel_pos += '1 1 0' * panel_bg_padding;
1266 panel_size -= '2 2 0' * panel_bg_padding;
1270 vector tmp = panel_size;
1272 float weapon_width = tmp.x / columnns / rows;
1275 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1279 // column highlighting
1280 for (int i = 0; i < columnns; ++i)
1282 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1285 for (int i = 0; i < rows; ++i)
1286 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1289 average_accuracy = 0;
1290 int weapons_with_stats = 0;
1292 pos.x += weapon_width / 2;
1294 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1297 Accuracy_LoadColors();
1299 float oldposx = pos.x;
1303 FOREACH(Weapons, it != WEP_Null, {
1304 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1306 WepSet set = it.m_wepset;
1307 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1309 if (it.spawnflags & WEP_TYPE_OTHER)
1313 if (weapon_stats >= 0)
1314 weapon_alpha = sbt_fg_alpha;
1316 weapon_alpha = 0.2 * sbt_fg_alpha;
1319 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1321 if (weapon_stats >= 0) {
1322 weapons_with_stats += 1;
1323 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1326 s = sprintf("%d%%", weapon_stats * 100);
1329 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1331 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1332 rgb = Accuracy_GetColor(weapon_stats);
1334 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1336 tmpos.x += weapon_width * rows;
1337 pos.x += weapon_width * rows;
1338 if (rows == 2 && column == columnns - 1) {
1346 if (weapons_with_stats)
1347 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1349 panel_size.x += panel_bg_padding * 2; // restore initial width
1354 .bool uninteresting;
1355 STATIC_INIT(default_order_items_label)
1357 IL_EACH(default_order_items, true, {
1358 if(!(it.instanceOfPowerup
1359 || it == ITEM_HealthMega || it == ITEM_HealthBig
1360 || it == ITEM_ArmorMega || it == ITEM_ArmorBig
1363 it.uninteresting = true;
1368 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1370 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1372 int disowned_cnt = 0;
1373 int uninteresting_cnt = 0;
1374 IL_EACH(default_order_items, true, {
1375 int q = g_inventory.inv_items[it.m_id];
1376 //q = 1; // debug: display all items
1377 if (autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting)
1378 ++uninteresting_cnt;
1382 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1383 int n = items_cnt - disowned_cnt;
1384 if (n <= 0) return pos;
1386 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1387 int columnns = max(6, ceil(n / rows));
1390 float fontsize = height * 1/3;
1391 float item_height = height * 2/3;
1393 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1394 pos.y += 1.25 * hud_fontsize.y;
1395 if(panel.current_panel_bg != "0")
1396 pos.y += panel_bg_border;
1399 panel_size.y = height * rows;
1400 panel_size.y += panel_bg_padding * 2;
1402 float panel_bg_alpha_save = panel_bg_alpha;
1403 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1405 panel_bg_alpha = panel_bg_alpha_save;
1407 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1408 if(panel.current_panel_bg != "0")
1409 end_pos.y += panel_bg_border * 2;
1411 if(panel_bg_padding)
1413 panel_pos += '1 1 0' * panel_bg_padding;
1414 panel_size -= '2 2 0' * panel_bg_padding;
1418 vector tmp = panel_size;
1420 float item_width = tmp.x / columnns / rows;
1423 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1427 // column highlighting
1428 for (int i = 0; i < columnns; ++i)
1430 drawfill(pos + '1 0 0' * item_width * rows * i, '0 1 0' * height * rows + '1 0 0' * item_width * rows, '0 0 0', sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1433 for (int i = 0; i < rows; ++i)
1434 drawfill(pos + '0 1 0' * item_height + '0 1 0' * height * i, '1 0 0' * panel_size.x + '0 1 0' * fontsize, rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1438 pos.x += item_width / 2;
1440 float oldposx = pos.x;
1444 IL_EACH(default_order_items, !(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting), {
1445 int n = g_inventory.inv_items[it.m_id];
1446 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1447 if (n <= 0) continue;
1448 drawpic_aspect_skin(tmpos, it.m_icon, '1 0 0' * item_width + '0 1 0' * item_height, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1450 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1451 drawstring(tmpos + '1 0 0' * padding + '0 1 0' * item_height, s, '1 1 0' * fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1452 tmpos.x += item_width * rows;
1453 pos.x += item_width * rows;
1454 if (rows == 2 && column == columnns - 1) {
1462 panel_size.x += panel_bg_padding * 2; // restore initial width
1467 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1469 pos.x += hud_fontsize.x * 0.25;
1470 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1471 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1472 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1474 pos.y += hud_fontsize.y;
1479 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1480 float stat_secrets_found, stat_secrets_total;
1481 float stat_monsters_killed, stat_monsters_total;
1485 // get monster stats
1486 stat_monsters_killed = STAT(MONSTERS_KILLED);
1487 stat_monsters_total = STAT(MONSTERS_TOTAL);
1489 // get secrets stats
1490 stat_secrets_found = STAT(SECRETS_FOUND);
1491 stat_secrets_total = STAT(SECRETS_TOTAL);
1493 // get number of rows
1494 if(stat_secrets_total)
1496 if(stat_monsters_total)
1499 // if no rows, return
1503 // draw table header
1504 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1505 pos.y += 1.25 * hud_fontsize.y;
1506 if(panel.current_panel_bg != "0")
1507 pos.y += panel_bg_border;
1510 panel_size.y = hud_fontsize.y * rows;
1511 panel_size.y += panel_bg_padding * 2;
1514 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1515 if(panel.current_panel_bg != "0")
1516 end_pos.y += panel_bg_border * 2;
1518 if(panel_bg_padding)
1520 panel_pos += '1 1 0' * panel_bg_padding;
1521 panel_size -= '2 2 0' * panel_bg_padding;
1525 vector tmp = panel_size;
1528 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1531 if(stat_monsters_total)
1533 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1534 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1538 if(stat_secrets_total)
1540 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1541 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1544 panel_size.x += panel_bg_padding * 2; // restore initial width
1549 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1552 RANKINGS_RECEIVED_CNT = 0;
1553 for (i=RANKINGS_CNT-1; i>=0; --i)
1555 ++RANKINGS_RECEIVED_CNT;
1557 if (RANKINGS_RECEIVED_CNT == 0)
1560 vector hl_rgb = rgb + '0.5 0.5 0.5';
1562 pos.y += hud_fontsize.y;
1563 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1564 pos.y += 1.25 * hud_fontsize.y;
1565 if(panel.current_panel_bg != "0")
1566 pos.y += panel_bg_border;
1571 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1573 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1578 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1580 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1584 float ranksize = 3 * hud_fontsize.x;
1585 float timesize = 5 * hud_fontsize.x;
1586 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1587 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1588 columns = min(columns, RANKINGS_RECEIVED_CNT);
1590 // expand name column to fill the entire row
1591 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1592 namesize += available_space;
1593 columnsize.x += available_space;
1595 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1596 panel_size.y += panel_bg_padding * 2;
1600 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1601 if(panel.current_panel_bg != "0")
1602 end_pos.y += panel_bg_border * 2;
1604 if(panel_bg_padding)
1606 panel_pos += '1 1 0' * panel_bg_padding;
1607 panel_size -= '2 2 0' * panel_bg_padding;
1613 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1615 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1617 int column = 0, j = 0;
1618 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1619 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1626 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1627 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1628 else if(!((j + column) & 1) && sbt_highlight)
1629 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1631 str = count_ordinal(i+1);
1632 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1633 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1634 str = ColorTranslateRGB(grecordholder[i]);
1636 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1637 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1639 pos.y += 1.25 * hud_fontsize.y;
1641 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1645 pos.x += panel_size.x / columns;
1646 pos.y = panel_pos.y;
1649 strfree(zoned_name_self);
1651 panel_size.x += panel_bg_padding * 2; // restore initial width
1655 float scoreboard_time;
1656 bool have_weapon_stats;
1657 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1659 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1661 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1664 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1665 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1671 if (!have_weapon_stats)
1673 FOREACH(Weapons, it != WEP_Null, {
1674 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1675 if (weapon_stats >= 0)
1677 have_weapon_stats = true;
1681 if (!have_weapon_stats)
1688 bool have_item_stats;
1689 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1691 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1693 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1696 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1697 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1703 if (!have_item_stats)
1705 IL_EACH(default_order_items, true, {
1706 if (!(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting))
1708 int q = g_inventory.inv_items[it.m_id];
1709 //q = 1; // debug: display all items
1712 have_item_stats = true;
1717 if (!have_item_stats)
1724 void Scoreboard_Draw()
1726 if(!autocvar__hud_configure)
1728 if(!hud_draw_maximized) return;
1730 // frametime checks allow to toggle the scoreboard even when the game is paused
1731 if(scoreboard_active) {
1732 if (scoreboard_fade_alpha == 0)
1733 scoreboard_time = time;
1734 if(hud_configure_menu_open == 1)
1735 scoreboard_fade_alpha = 1;
1736 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1737 if (scoreboard_fadeinspeed && frametime)
1738 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1740 scoreboard_fade_alpha = 1;
1741 if(hud_fontsize_str != autocvar_hud_fontsize)
1743 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1744 Scoreboard_initFieldSizes();
1745 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1749 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1750 if (scoreboard_fadeoutspeed && frametime)
1751 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1753 scoreboard_fade_alpha = 0;
1756 if (!scoreboard_fade_alpha)
1758 scoreboard_acc_fade_alpha = 0;
1759 scoreboard_itemstats_fade_alpha = 0;
1764 scoreboard_fade_alpha = 0;
1766 if (autocvar_hud_panel_scoreboard_dynamichud)
1769 HUD_Scale_Disable();
1771 if(scoreboard_fade_alpha <= 0)
1773 panel_fade_alpha *= scoreboard_fade_alpha;
1774 HUD_Panel_LoadCvars();
1776 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1777 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1778 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1779 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1780 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1781 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1782 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1784 // don't overlap with con_notify
1785 if(!autocvar__hud_configure)
1786 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1788 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1789 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1790 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1791 panel_size.x = fixed_scoreboard_width;
1793 Scoreboard_UpdatePlayerTeams();
1795 float initial_pos_y = panel_pos.y;
1796 vector pos = panel_pos;
1801 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1803 // Begin of Game Info Section
1804 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1805 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1807 // Game Info: Game Type
1808 str = MapInfo_Type_ToText(gametype);
1809 draw_beginBoldFont();
1810 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_type_fontsize)), str, sb_gameinfo_type_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1813 // Game Info: Game Detail
1814 float tl = STAT(TIMELIMIT);
1815 float fl = STAT(FRAGLIMIT);
1816 float ll = STAT(LEADLIMIT);
1817 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1820 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1821 if(!gametype.m_hidelimits)
1826 str = strcat(str, "^7 / "); // delimiter
1829 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1830 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1831 (teamscores_label(ts_primary) == "fastest") ? "" :
1832 TranslateScoresLabel(teamscores_label(ts_primary))));
1836 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1837 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1838 (scores_label(ps_primary) == "fastest") ? "" :
1839 TranslateScoresLabel(scores_label(ps_primary))));
1844 if(tl > 0 || fl > 0)
1847 if (ll_and_fl && fl > 0)
1848 str = strcat(str, "^7 & ");
1850 str = strcat(str, "^7 / ");
1855 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1856 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1857 (teamscores_label(ts_primary) == "fastest") ? "" :
1858 TranslateScoresLabel(teamscores_label(ts_primary))));
1862 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1863 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1864 (scores_label(ps_primary) == "fastest") ? "" :
1865 TranslateScoresLabel(scores_label(ps_primary))));
1870 pos.y += sb_gameinfo_type_fontsize.y;
1871 drawcolorcodedstring(pos + '1 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_detail_fontsize)), str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align right
1873 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1874 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1875 // End of Game Info Section
1877 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1878 if(panel.current_panel_bg != "0")
1879 pos.y += panel_bg_border;
1881 // Draw the scoreboard
1882 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1885 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1889 vector panel_bg_color_save = panel_bg_color;
1890 vector team_score_baseoffset;
1891 vector team_size_baseoffset;
1892 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1894 // put team score to the left of scoreboard (and team size to the right)
1895 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1896 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1897 if(panel.current_panel_bg != "0")
1899 team_score_baseoffset.x -= panel_bg_border;
1900 team_size_baseoffset.x += panel_bg_border;
1905 // put team score to the right of scoreboard (and team size to the left)
1906 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1907 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1908 if(panel.current_panel_bg != "0")
1910 team_score_baseoffset.x += panel_bg_border;
1911 team_size_baseoffset.x -= panel_bg_border;
1915 int team_size_total = 0;
1916 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1918 // calculate team size total (sum of all team sizes)
1919 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1920 if(tm.team != NUM_SPECTATOR)
1921 team_size_total += tm.team_size;
1924 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1926 if(tm.team == NUM_SPECTATOR)
1931 draw_beginBoldFont();
1932 vector rgb = Team_ColorRGB(tm.team);
1933 str = ftos(tm.(teamscores(ts_primary)));
1934 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1936 // team score on the left (default)
1937 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1941 // team score on the right
1942 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1944 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1946 // team size (if set to show on the side)
1947 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1949 // calculate the starting position for the whole team size info string
1950 str = sprintf("%d/%d", tm.team_size, team_size_total);
1951 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1953 // team size on the left
1954 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1958 // team size on the right
1959 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1961 str = sprintf("%d", tm.team_size);
1962 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1963 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1964 str = sprintf("/%d", team_size_total);
1965 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1969 // secondary score, e.g. keyhunt
1970 if(ts_primary != ts_secondary)
1972 str = ftos(tm.(teamscores(ts_secondary)));
1973 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1976 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1981 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1984 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1987 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1988 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1989 else if(panel_bg_color_team > 0)
1990 panel_bg_color = rgb * panel_bg_color_team;
1992 panel_bg_color = rgb;
1993 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1995 panel_bg_color = panel_bg_color_save;
1999 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2000 if(tm.team != NUM_SPECTATOR)
2003 // display it anyway
2004 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2007 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2008 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2009 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2010 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2012 if(MUTATOR_CALLHOOK(ShowRankings)) {
2013 string ranktitle = M_ARGV(0, string);
2014 if(race_speedaward) {
2015 drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, race_speedaward_unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2016 pos.y += 1.25 * hud_fontsize.y;
2018 if(race_speedaward_alltimebest) {
2019 drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_unit, ColorTranslateRGB(race_speedaward_alltimebest_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2020 pos.y += 1.25 * hud_fontsize.y;
2022 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2025 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2028 for(pl = players.sort_next; pl; pl = pl.sort_next)
2030 if(pl.team == NUM_SPECTATOR)
2032 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2033 if(tm.team == NUM_SPECTATOR)
2035 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2036 draw_beginBoldFont();
2037 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2039 pos.y += 1.25 * hud_fontsize.y;
2041 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2042 pos.y += 1.25 * hud_fontsize.y;
2049 // print information about respawn status
2050 float respawn_time = STAT(RESPAWN_TIME);
2054 if(respawn_time < 0)
2056 // a negative number means we are awaiting respawn, time value is still the same
2057 respawn_time *= -1; // remove mark now that we checked it
2059 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2060 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2062 str = sprintf(_("^1Respawning in ^3%s^1..."),
2063 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2064 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2066 count_seconds(ceil(respawn_time - time))
2070 else if(time < respawn_time)
2072 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2073 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2074 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2076 count_seconds(ceil(respawn_time - time))
2080 else if(time >= respawn_time)
2081 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2083 pos.y += 1.2 * hud_fontsize.y;
2084 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2087 pos.y += 2 * hud_fontsize.y;
2088 if (scoreboard_fade_alpha < 1)
2089 scoreboard_bottom = initial_pos_y + (pos.y - initial_pos_y) * scoreboard_fade_alpha;
2090 else if (pos.y != scoreboard_bottom)
2092 if (pos.y > scoreboard_bottom)
2093 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - initial_pos_y));
2095 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - initial_pos_y));