1 #include "scoreboard.qh"
3 #include <client/draw.qh>
4 #include <client/hud/panel/chat.qh>
5 #include <client/hud/panel/physics.qh>
6 #include <client/hud/panel/quickmenu.qh>
7 #include <client/hud/panel/racetimer.qh>
8 #include <client/hud/panel/weapons.qh>
9 #include <common/constants.qh>
10 #include <common/ent_cs.qh>
11 #include <common/mapinfo.qh>
12 #include <common/minigames/cl_minigames.qh>
13 #include <common/net_linked.qh>
14 #include <common/scores.qh>
15 #include <common/stats.qh>
16 #include <common/teams.qh>
17 #include <common/items/inventory.qh>
21 void Scoreboard_Draw_Export(int fh)
23 // allow saving cvars that aesthetically change the panel into hud skin files
24 HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
25 HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
26 HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
27 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
28 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
29 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
30 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
31 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
32 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
33 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
34 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_eliminated");
35 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
36 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
37 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
38 HUD_Write_Cvar("hud_panel_scoreboard_spectators_position");
41 const int MAX_SBT_FIELDS = MAX_SCORE;
43 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
44 float sbt_field_size[MAX_SBT_FIELDS + 1];
45 string sbt_field_title[MAX_SBT_FIELDS + 1];
48 string autocvar_hud_fontsize;
49 string hud_fontsize_str;
54 float sbt_fg_alpha_self;
56 float sbt_highlight_alpha;
57 float sbt_highlight_alpha_self;
58 float sbt_highlight_alpha_eliminated;
60 // provide basic panel cvars to old clients
61 // TODO remove them after a future release (0.8.2+)
62 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
63 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
64 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
65 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
66 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
67 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
68 noref string autocvar_hud_panel_scoreboard_bg_border = "";
69 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
71 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
72 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
73 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
74 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
75 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
76 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
77 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
78 bool autocvar_hud_panel_scoreboard_table_highlight = true;
79 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
80 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
81 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
82 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
83 float autocvar_hud_panel_scoreboard_namesize = 15;
84 float autocvar_hud_panel_scoreboard_team_size_position = 0;
85 float autocvar_hud_panel_scoreboard_spectators_position = 1;
87 bool autocvar_hud_panel_scoreboard_accuracy = true;
88 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
89 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
90 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
91 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
93 bool autocvar_hud_panel_scoreboard_itemstats = true;
94 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
95 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
96 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
97 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
98 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
100 bool autocvar_hud_panel_scoreboard_dynamichud = false;
102 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
103 bool autocvar_hud_panel_scoreboard_others_showscore = true;
104 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
105 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
106 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
107 bool autocvar_hud_panel_scoreboard_playerid = false;
108 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
109 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
111 // mode 0: returns translated label
112 // mode 1: prints name and description of all the labels
113 string Label_getInfo(string label, int mode)
116 label = "bckills"; // first case in the switch
120 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
121 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
122 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")));
123 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
124 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
125 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
126 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
127 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
128 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
129 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
130 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
131 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
132 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
133 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
134 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
135 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
136 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
137 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
138 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
139 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
140 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
141 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
142 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
143 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
144 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
145 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
146 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
147 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")));
148 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
149 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
150 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
151 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
152 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
153 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
154 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
155 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
156 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
157 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
158 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
159 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
160 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
161 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
162 default: return label;
167 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
168 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
170 #define SB_EXTRA_SORTING_FIELDS 5
171 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
172 void Scoreboard_InitScores()
176 ps_primary = ps_secondary = NULL;
177 ts_primary = ts_secondary = -1;
178 FOREACH(Scores, true, {
179 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
180 if(f == SFL_SORT_PRIO_PRIMARY)
182 if(f == SFL_SORT_PRIO_SECONDARY)
184 if(ps_primary == it || ps_secondary == it)
186 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
187 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
188 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
189 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
190 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
192 if(ps_secondary == NULL)
193 ps_secondary = ps_primary;
195 for(i = 0; i < MAX_TEAMSCORE; ++i)
197 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
198 if(f == SFL_SORT_PRIO_PRIMARY)
200 if(f == SFL_SORT_PRIO_SECONDARY)
203 if(ts_secondary == -1)
204 ts_secondary = ts_primary;
206 Cmd_Scoreboard_SetFields(0);
210 void Scoreboard_UpdatePlayerTeams()
214 for(pl = players.sort_next; pl; pl = pl.sort_next)
217 int Team = entcs_GetScoreTeam(pl.sv_entnum);
218 if(SetTeam(pl, Team))
221 Scoreboard_UpdatePlayerPos(pl);
225 pl = players.sort_next;
230 print(strcat("PNUM: ", ftos(num), "\n"));
235 int Scoreboard_CompareScore(int vl, int vr, int f)
237 TC(int, vl); TC(int, vr); TC(int, f);
238 if(f & SFL_ZERO_IS_WORST)
240 if(vl == 0 && vr != 0)
242 if(vl != 0 && vr == 0)
246 return IS_INCREASING(f);
248 return IS_DECREASING(f);
252 float Scoreboard_ComparePlayerScores(entity left, entity right)
254 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
255 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
262 if(vl == NUM_SPECTATOR)
264 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
266 if(!left.gotscores && right.gotscores)
273 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
277 if (!fld) fld = ps_primary;
278 else if (ps_secondary == ps_primary) continue;
279 else fld = ps_secondary;
283 fld = sb_extra_sorting_field[i];
284 if (fld == ps_primary || fld == ps_secondary) continue;
288 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
289 if (r >= 0) return r;
292 if (left.sv_entnum < right.sv_entnum)
298 void Scoreboard_UpdatePlayerPos(entity player)
301 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
303 SORT_SWAP(player, ent);
305 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
307 SORT_SWAP(ent, player);
311 float Scoreboard_CompareTeamScores(entity left, entity right)
313 if(left.team == NUM_SPECTATOR)
315 if(right.team == NUM_SPECTATOR)
320 for(int i = -2; i < MAX_TEAMSCORE; ++i)
324 if (fld_idx == -1) fld_idx = ts_primary;
325 else if (ts_secondary == ts_primary) continue;
326 else fld_idx = ts_secondary;
331 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
334 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
335 if (r >= 0) return r;
338 if (left.team < right.team)
344 void Scoreboard_UpdateTeamPos(entity Team)
347 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
349 SORT_SWAP(Team, ent);
351 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
353 SORT_SWAP(ent, Team);
357 void Cmd_Scoreboard_Help()
359 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
360 LOG_HELP(_("Usage:"));
361 LOG_HELP("^2scoreboard_columns_set ^3default");
362 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
363 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
364 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
365 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
366 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
367 LOG_HELP(_("The following field names are recognized (case insensitive):"));
373 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
374 "of game types, then a slash, to make the field show up only in these\n"
375 "or in all but these game types. You can also specify 'all' as a\n"
376 "field to show all fields available for the current game mode."));
379 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
380 "include/exclude ALL teams/noteams game modes."));
383 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
384 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
385 "right of the vertical bar aligned to the right."));
386 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
387 "other gamemodes except DM."));
390 // NOTE: adding a gametype with ? to not warn for an optional field
391 // make sure it's excluded in a previous exclusive rule, if any
392 // otherwise the previous exclusive rule warns anyway
393 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
394 #define SCOREBOARD_DEFAULT_COLUMNS \
395 "ping pl fps name |" \
396 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
397 " -teams,lms/deaths +ft,tdm/deaths" \
399 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
400 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
401 " +tdm,ft,dom,ons,as/teamkills"\
402 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
403 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
404 " +lms/lives +lms/rank" \
405 " +kh/kckills +kh/losses +kh/caps" \
406 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
407 " +as/objectives +nb/faults +nb/goals" \
408 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
409 " +dom/ticks +dom/takes" \
410 " -lms,rc,cts,inv,nb/score"
412 void Cmd_Scoreboard_SetFields(int argc)
417 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
421 return; // do nothing, we don't know gametype and scores yet
423 // sbt_fields uses strunzone on the titles!
424 if(!sbt_field_title[0])
425 for(i = 0; i < MAX_SBT_FIELDS; ++i)
426 sbt_field_title[i] = strzone("(null)");
428 // TODO: re enable with gametype dependant cvars?
429 if(argc < 3) // no arguments provided
430 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
433 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
437 if(argv(2) == "default" || argv(2) == "expand_default")
439 if(argv(2) == "expand_default")
440 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
441 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
443 else if(argv(2) == "all" || argv(2) == "ALL")
445 string s = "ping pl name |"; // scores without label (not really scores)
448 // scores without label
449 s = strcat(s, " ", "sum");
450 s = strcat(s, " ", "kdratio");
451 s = strcat(s, " ", "frags");
453 FOREACH(Scores, true, {
455 if(it != ps_secondary)
456 if(scores_label(it) != "")
457 s = strcat(s, " ", scores_label(it));
459 if(ps_secondary != ps_primary)
460 s = strcat(s, " ", scores_label(ps_secondary));
461 s = strcat(s, " ", scores_label(ps_primary));
462 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
469 hud_fontsize = HUD_GetFontsize("hud_fontsize");
471 for(i = 1; i < argc - 1; ++i)
474 bool nocomplain = false;
475 if(substring(str, 0, 1) == "?")
478 str = substring(str, 1, strlen(str) - 1);
481 slash = strstrofs(str, "/", 0);
484 pattern = substring(str, 0, slash);
485 str = substring(str, slash + 1, strlen(str) - (slash + 1));
487 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
491 str = strtolower(str);
492 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
493 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
498 // fields without a label (not networked via the score system)
499 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
500 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
501 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
502 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
503 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
504 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
505 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
506 default: // fields with a label
508 // map alternative labels
509 if (str == "damage") str = "dmg";
510 if (str == "damagetaken") str = "dmgtaken";
512 FOREACH(Scores, true, {
513 if (str == strtolower(scores_label(it))) {
515 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
519 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
520 if(!nocomplain && str != "fps") // server can disable the fps field
521 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
523 strfree(sbt_field_title[sbt_num_fields]);
524 sbt_field_size[sbt_num_fields] = 0;
528 sbt_field[sbt_num_fields] = j;
531 if(j == ps_secondary)
532 have_secondary = true;
537 if(sbt_num_fields >= MAX_SBT_FIELDS)
541 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
543 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
544 have_secondary = true;
545 if(ps_primary == ps_secondary)
546 have_secondary = true;
547 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
549 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
553 strfree(sbt_field_title[sbt_num_fields]);
554 for(i = sbt_num_fields; i > 0; --i)
556 sbt_field_title[i] = sbt_field_title[i-1];
557 sbt_field_size[i] = sbt_field_size[i-1];
558 sbt_field[i] = sbt_field[i-1];
560 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
561 sbt_field[0] = SP_NAME;
563 LOG_INFO("fixed missing field 'name'");
567 strfree(sbt_field_title[sbt_num_fields]);
568 for(i = sbt_num_fields; i > 1; --i)
570 sbt_field_title[i] = sbt_field_title[i-1];
571 sbt_field_size[i] = sbt_field_size[i-1];
572 sbt_field[i] = sbt_field[i-1];
574 sbt_field_title[1] = strzone("|");
575 sbt_field[1] = SP_SEPARATOR;
576 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
578 LOG_INFO("fixed missing field '|'");
581 else if(!have_separator)
583 strcpy(sbt_field_title[sbt_num_fields], "|");
584 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
585 sbt_field[sbt_num_fields] = SP_SEPARATOR;
587 LOG_INFO("fixed missing field '|'");
591 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
592 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
593 sbt_field[sbt_num_fields] = ps_secondary;
595 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
599 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
600 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
601 sbt_field[sbt_num_fields] = ps_primary;
603 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
607 sbt_field[sbt_num_fields] = SP_END;
610 string Scoreboard_AddPlayerId(string pl_name, entity pl)
612 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
613 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
614 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
618 vector sbt_field_rgb;
619 string sbt_field_icon0;
620 string sbt_field_icon1;
621 string sbt_field_icon2;
622 vector sbt_field_icon0_rgb;
623 vector sbt_field_icon1_rgb;
624 vector sbt_field_icon2_rgb;
625 string Scoreboard_GetName(entity pl)
627 if(ready_waiting && pl.ready)
629 sbt_field_icon0 = "gfx/scoreboard/player_ready";
633 int f = entcs_GetClientColors(pl.sv_entnum);
635 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
636 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
637 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
638 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
639 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
642 return entcs_GetName(pl.sv_entnum);
645 string Scoreboard_GetField(entity pl, PlayerScoreField field)
647 float tmp, num, denom;
650 sbt_field_rgb = '1 1 1';
651 sbt_field_icon0 = "";
652 sbt_field_icon1 = "";
653 sbt_field_icon2 = "";
654 sbt_field_icon0_rgb = '1 1 1';
655 sbt_field_icon1_rgb = '1 1 1';
656 sbt_field_icon2_rgb = '1 1 1';
661 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
662 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
666 tmp = max(0, min(220, f-80)) / 220;
667 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
673 f = pl.ping_packetloss;
674 tmp = pl.ping_movementloss;
675 if(f == 0 && tmp == 0)
677 str = ftos(ceil(f * 100));
679 str = strcat(str, "~", ftos(ceil(tmp * 100)));
680 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
681 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
685 str = Scoreboard_GetName(pl);
686 if (autocvar_hud_panel_scoreboard_playerid)
687 str = Scoreboard_AddPlayerId(str, pl);
691 f = pl.(scores(SP_KILLS));
692 f -= pl.(scores(SP_SUICIDES));
696 num = pl.(scores(SP_KILLS));
697 denom = pl.(scores(SP_DEATHS));
700 sbt_field_rgb = '0 1 0';
701 str = sprintf("%d", num);
702 } else if(num <= 0) {
703 sbt_field_rgb = '1 0 0';
704 str = sprintf("%.1f", num/denom);
706 str = sprintf("%.1f", num/denom);
710 f = pl.(scores(SP_KILLS));
711 f -= pl.(scores(SP_DEATHS));
714 sbt_field_rgb = '0 1 0';
716 sbt_field_rgb = '1 1 1';
718 sbt_field_rgb = '1 0 0';
724 float elo = pl.(scores(SP_ELO));
726 case -1: return "...";
727 case -2: return _("N/A");
728 default: return ftos(elo);
734 float fps = pl.(scores(SP_FPS));
737 sbt_field_rgb = '1 1 1';
738 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
740 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
741 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
745 case SP_DMG: case SP_DMGTAKEN:
746 return sprintf("%.1f k", pl.(scores(field)) / 1000);
748 default: case SP_SCORE:
749 tmp = pl.(scores(field));
750 f = scores_flags(field);
751 if(field == ps_primary)
752 sbt_field_rgb = '1 1 0';
753 else if(field == ps_secondary)
754 sbt_field_rgb = '0 1 1';
756 sbt_field_rgb = '1 1 1';
757 return ScoreString(f, tmp);
762 float sbt_fixcolumnwidth_len;
763 float sbt_fixcolumnwidth_iconlen;
764 float sbt_fixcolumnwidth_marginlen;
766 string Scoreboard_FixColumnWidth(int i, string str)
772 sbt_fixcolumnwidth_iconlen = 0;
774 if(sbt_field_icon0 != "")
776 sz = draw_getimagesize(sbt_field_icon0);
778 if(sbt_fixcolumnwidth_iconlen < f)
779 sbt_fixcolumnwidth_iconlen = f;
782 if(sbt_field_icon1 != "")
784 sz = draw_getimagesize(sbt_field_icon1);
786 if(sbt_fixcolumnwidth_iconlen < f)
787 sbt_fixcolumnwidth_iconlen = f;
790 if(sbt_field_icon2 != "")
792 sz = draw_getimagesize(sbt_field_icon2);
794 if(sbt_fixcolumnwidth_iconlen < f)
795 sbt_fixcolumnwidth_iconlen = f;
798 if(sbt_fixcolumnwidth_iconlen != 0)
800 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
801 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
804 sbt_fixcolumnwidth_marginlen = 0;
806 if(sbt_field[i] == SP_NAME) // name gets all remaining space
809 float remaining_space = 0;
810 for(j = 0; j < sbt_num_fields; ++j)
812 if (sbt_field[i] != SP_SEPARATOR)
813 remaining_space += sbt_field_size[j] + hud_fontsize.x;
814 sbt_field_size[i] = panel_size.x - remaining_space;
816 if (sbt_fixcolumnwidth_iconlen != 0)
817 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
818 float namesize = panel_size.x - remaining_space;
819 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
820 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
822 max_namesize = vid_conwidth - remaining_space;
825 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
827 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
828 if(sbt_field_size[i] < f)
829 sbt_field_size[i] = f;
834 void Scoreboard_initFieldSizes()
836 for(int i = 0; i < sbt_num_fields; ++i)
838 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
839 Scoreboard_FixColumnWidth(i, "");
843 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
846 vector column_dim = eY * panel_size.y;
848 column_dim.y -= 1.25 * hud_fontsize.y;
849 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
850 pos.x += hud_fontsize.x * 0.5;
851 for(i = 0; i < sbt_num_fields; ++i)
853 if(sbt_field[i] == SP_SEPARATOR)
855 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
858 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
859 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
860 pos.x += column_dim.x;
862 if(sbt_field[i] == SP_SEPARATOR)
864 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
865 for(i = sbt_num_fields - 1; i > 0; --i)
867 if(sbt_field[i] == SP_SEPARATOR)
870 pos.x -= sbt_field_size[i];
875 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
876 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
879 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
880 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
881 pos.x -= hud_fontsize.x;
886 pos.y += 1.25 * hud_fontsize.y;
890 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
892 TC(bool, is_self); TC(int, pl_number);
894 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
896 vector h_pos = item_pos;
897 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
898 // alternated rows highlighting
900 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
901 else if((sbt_highlight) && (!(pl_number % 2)))
902 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
904 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
906 vector pos = item_pos;
907 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
909 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
911 pos.x += hud_fontsize.x * 0.5;
912 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
913 vector tmp = '0 0 0';
915 PlayerScoreField field;
916 for(i = 0; i < sbt_num_fields; ++i)
918 field = sbt_field[i];
919 if(field == SP_SEPARATOR)
922 if(is_spec && field != SP_NAME && field != SP_PING) {
923 pos.x += sbt_field_size[i] + hud_fontsize.x;
926 str = Scoreboard_GetField(pl, field);
927 str = Scoreboard_FixColumnWidth(i, str);
929 pos.x += sbt_field_size[i] + hud_fontsize.x;
931 if(field == SP_NAME) {
932 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
933 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
935 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
936 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
939 tmp.x = sbt_field_size[i] + hud_fontsize.x;
940 if(sbt_field_icon0 != "")
941 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
942 if(sbt_field_icon1 != "")
943 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
944 if(sbt_field_icon2 != "")
945 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
948 if(sbt_field[i] == SP_SEPARATOR)
950 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
951 for(i = sbt_num_fields-1; i > 0; --i)
953 field = sbt_field[i];
954 if(field == SP_SEPARATOR)
957 if(is_spec && field != SP_NAME && field != SP_PING) {
958 pos.x -= sbt_field_size[i] + hud_fontsize.x;
962 str = Scoreboard_GetField(pl, field);
963 str = Scoreboard_FixColumnWidth(i, str);
965 if(field == SP_NAME) {
966 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
967 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
969 tmp.x = sbt_fixcolumnwidth_len;
970 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
973 tmp.x = sbt_field_size[i];
974 if(sbt_field_icon0 != "")
975 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
976 if(sbt_field_icon1 != "")
977 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
978 if(sbt_field_icon2 != "")
979 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
980 pos.x -= sbt_field_size[i] + hud_fontsize.x;
985 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
988 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
991 vector h_pos = item_pos;
992 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
994 bool complete = (this_team == NUM_SPECTATOR);
997 if((sbt_highlight) && (!(pl_number % 2)))
998 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1000 vector pos = item_pos;
1001 pos.x += hud_fontsize.x * 0.5;
1002 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1004 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1006 width_limit -= stringwidth("...", false, hud_fontsize);
1007 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1008 static float max_name_width = 0;
1010 float fieldsize = 0;
1011 float min_fieldsize = 0;
1012 float fieldpadding = hud_fontsize.x * 0.25;
1013 if(this_team == NUM_SPECTATOR)
1015 if(autocvar_hud_panel_scoreboard_spectators_showping)
1016 min_fieldsize = stringwidth("999", false, hud_fontsize);
1018 else if(autocvar_hud_panel_scoreboard_others_showscore)
1019 min_fieldsize = stringwidth("99", false, hud_fontsize);
1020 for(i = 0; pl; pl = pl.sort_next)
1022 if(pl.team != this_team)
1024 if(pl == ignored_pl)
1028 if(this_team == NUM_SPECTATOR)
1030 if(autocvar_hud_panel_scoreboard_spectators_showping)
1031 field = Scoreboard_GetField(pl, SP_PING);
1033 else if(autocvar_hud_panel_scoreboard_others_showscore)
1034 field = Scoreboard_GetField(pl, SP_SCORE);
1036 string str = entcs_GetName(pl.sv_entnum);
1037 if (autocvar_hud_panel_scoreboard_playerid)
1038 str = Scoreboard_AddPlayerId(str, pl);
1039 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1040 float column_width = stringwidth(str, true, hud_fontsize);
1041 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1043 if(column_width > max_name_width)
1044 max_name_width = column_width;
1045 column_width = max_name_width;
1049 fieldsize = stringwidth(field, false, hud_fontsize);
1050 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1053 if(pos.x + column_width > width_limit)
1058 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1063 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1064 pos.y += hud_fontsize.y * 1.25;
1068 vector name_pos = pos;
1069 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1070 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1071 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1074 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1075 h_size.y = hud_fontsize.y;
1076 vector field_pos = pos;
1077 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1078 field_pos.x += column_width - h_size.x;
1080 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1081 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1082 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1086 h_size.x = column_width + hud_fontsize.x * 0.25;
1087 h_size.y = hud_fontsize.y;
1088 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1090 pos.x += column_width;
1091 pos.x += hud_fontsize.x;
1093 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1096 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1098 int max_players = 999;
1099 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1101 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1104 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1105 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1106 height /= team_count;
1109 height -= panel_bg_padding * 2; // - padding
1110 max_players = floor(height / (hud_fontsize.y * 1.25));
1111 if(max_players <= 1)
1113 if(max_players == tm.team_size)
1118 entity me = playerslots[current_player];
1120 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1121 panel_size.y += panel_bg_padding * 2;
1124 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1125 if(panel.current_panel_bg != "0")
1126 end_pos.y += panel_bg_border * 2;
1128 if(panel_bg_padding)
1130 panel_pos += '1 1 0' * panel_bg_padding;
1131 panel_size -= '2 2 0' * panel_bg_padding;
1135 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1139 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1141 pos.y += 1.25 * hud_fontsize.y;
1144 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1146 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1149 // print header row and highlight columns
1150 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1152 // fill the table and draw the rows
1153 bool is_self = false;
1154 bool self_shown = false;
1156 for(pl = players.sort_next; pl; pl = pl.sort_next)
1158 if(pl.team != tm.team)
1160 if(i == max_players - 2 && pl != me)
1162 if(!self_shown && me.team == tm.team)
1164 Scoreboard_DrawItem(pos, rgb, me, true, i);
1166 pos.y += 1.25 * hud_fontsize.y;
1170 if(i >= max_players - 1)
1172 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1175 is_self = (pl.sv_entnum == current_player);
1176 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1179 pos.y += 1.25 * hud_fontsize.y;
1183 panel_size.x += panel_bg_padding * 2; // restore initial width
1187 bool Scoreboard_WouldDraw()
1189 if (MUTATOR_CALLHOOK(DrawScoreboard))
1191 else if (QuickMenu_IsOpened())
1193 else if (HUD_Radar_Clickable())
1195 else if (scoreboard_showscores)
1197 else if (intermission == 1)
1199 else if (intermission == 2)
1201 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1202 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1206 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1211 float average_accuracy;
1212 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1214 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1216 WepSet weapons_stat = WepSet_GetFromStat();
1217 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1218 int disownedcnt = 0;
1220 FOREACH(Weapons, it != WEP_Null, {
1221 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1223 WepSet set = it.m_wepset;
1224 if(it.spawnflags & WEP_TYPE_OTHER)
1229 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1231 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1238 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1239 if (weapon_cnt <= 0) return pos;
1242 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1244 int columnns = ceil(weapon_cnt / rows);
1246 float weapon_height = hud_fontsize.y * 2.3;
1247 float height = weapon_height + hud_fontsize.y;
1249 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);
1250 pos.y += 1.25 * hud_fontsize.y;
1251 if(panel.current_panel_bg != "0")
1252 pos.y += panel_bg_border;
1255 panel_size.y = height * rows;
1256 panel_size.y += panel_bg_padding * 2;
1258 float panel_bg_alpha_save = panel_bg_alpha;
1259 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1261 panel_bg_alpha = panel_bg_alpha_save;
1263 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1264 if(panel.current_panel_bg != "0")
1265 end_pos.y += panel_bg_border * 2;
1267 if(panel_bg_padding)
1269 panel_pos += '1 1 0' * panel_bg_padding;
1270 panel_size -= '2 2 0' * panel_bg_padding;
1274 vector tmp = panel_size;
1276 float weapon_width = tmp.x / columnns / rows;
1279 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1283 // column highlighting
1284 for (int i = 0; i < columnns; ++i)
1286 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);
1289 for (int i = 0; i < rows; ++i)
1290 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1293 average_accuracy = 0;
1294 int weapons_with_stats = 0;
1296 pos.x += weapon_width / 2;
1298 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1301 Accuracy_LoadColors();
1303 float oldposx = pos.x;
1307 FOREACH(Weapons, it != WEP_Null, {
1308 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1310 WepSet set = it.m_wepset;
1311 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1313 if (it.spawnflags & WEP_TYPE_OTHER)
1317 if (weapon_stats >= 0)
1318 weapon_alpha = sbt_fg_alpha;
1320 weapon_alpha = 0.2 * sbt_fg_alpha;
1323 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1325 if (weapon_stats >= 0) {
1326 weapons_with_stats += 1;
1327 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1330 s = sprintf("%d%%", weapon_stats * 100);
1333 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1335 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1336 rgb = Accuracy_GetColor(weapon_stats);
1338 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1340 tmpos.x += weapon_width * rows;
1341 pos.x += weapon_width * rows;
1342 if (rows == 2 && column == columnns - 1) {
1350 if (weapons_with_stats)
1351 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1353 panel_size.x += panel_bg_padding * 2; // restore initial width
1358 bool is_item_filtered(entity it)
1360 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1362 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1365 if (it.instanceOfArmor || it.instanceOfHealth)
1367 int ha_mask = floor(mask) % 10;
1370 default: return false;
1371 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1372 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1373 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1374 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1377 if (it.instanceOfAmmo)
1379 int ammo_mask = floor(mask / 10) % 10;
1380 return (ammo_mask == 1);
1385 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1387 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1389 int disowned_cnt = 0;
1390 int uninteresting_cnt = 0;
1391 IL_EACH(default_order_items, true, {
1392 int q = g_inventory.inv_items[it.m_id];
1393 //q = 1; // debug: display all items
1394 if (is_item_filtered(it))
1395 ++uninteresting_cnt;
1399 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1400 int n = items_cnt - disowned_cnt;
1401 if (n <= 0) return pos;
1403 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1404 int columnns = max(6, ceil(n / rows));
1406 float item_height = hud_fontsize.y * 2.3;
1407 float height = item_height + hud_fontsize.y;
1409 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1410 pos.y += 1.25 * hud_fontsize.y;
1411 if(panel.current_panel_bg != "0")
1412 pos.y += panel_bg_border;
1415 panel_size.y = height * rows;
1416 panel_size.y += panel_bg_padding * 2;
1418 float panel_bg_alpha_save = panel_bg_alpha;
1419 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1421 panel_bg_alpha = panel_bg_alpha_save;
1423 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1424 if(panel.current_panel_bg != "0")
1425 end_pos.y += panel_bg_border * 2;
1427 if(panel_bg_padding)
1429 panel_pos += '1 1 0' * panel_bg_padding;
1430 panel_size -= '2 2 0' * panel_bg_padding;
1434 vector tmp = panel_size;
1436 float item_width = tmp.x / columnns / rows;
1439 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1443 // column highlighting
1444 for (int i = 0; i < columnns; ++i)
1446 drawfill(pos + eX * item_width * rows * i, vec2(item_width * rows, height * rows), '0 0 0', sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1449 for (int i = 0; i < rows; ++i)
1450 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1454 pos.x += item_width / 2;
1456 float oldposx = pos.x;
1460 IL_EACH(default_order_items, !is_item_filtered(it), {
1461 int n = g_inventory.inv_items[it.m_id];
1462 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1463 if (n <= 0) continue;
1464 drawpic_aspect_skin(tmpos, it.m_icon, eX * item_width + eY * item_height, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1466 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1467 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1468 tmpos.x += item_width * rows;
1469 pos.x += item_width * rows;
1470 if (rows == 2 && column == columnns - 1) {
1478 panel_size.x += panel_bg_padding * 2; // restore initial width
1483 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1485 pos.x += hud_fontsize.x * 0.25;
1486 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1487 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1488 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1490 pos.y += hud_fontsize.y;
1495 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1496 float stat_secrets_found, stat_secrets_total;
1497 float stat_monsters_killed, stat_monsters_total;
1501 // get monster stats
1502 stat_monsters_killed = STAT(MONSTERS_KILLED);
1503 stat_monsters_total = STAT(MONSTERS_TOTAL);
1505 // get secrets stats
1506 stat_secrets_found = STAT(SECRETS_FOUND);
1507 stat_secrets_total = STAT(SECRETS_TOTAL);
1509 // get number of rows
1510 if(stat_secrets_total)
1512 if(stat_monsters_total)
1515 // if no rows, return
1519 // draw table header
1520 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1521 pos.y += 1.25 * hud_fontsize.y;
1522 if(panel.current_panel_bg != "0")
1523 pos.y += panel_bg_border;
1526 panel_size.y = hud_fontsize.y * rows;
1527 panel_size.y += panel_bg_padding * 2;
1530 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1531 if(panel.current_panel_bg != "0")
1532 end_pos.y += panel_bg_border * 2;
1534 if(panel_bg_padding)
1536 panel_pos += '1 1 0' * panel_bg_padding;
1537 panel_size -= '2 2 0' * panel_bg_padding;
1541 vector tmp = panel_size;
1544 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1547 if(stat_monsters_total)
1549 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1550 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1554 if(stat_secrets_total)
1556 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1557 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1560 panel_size.x += panel_bg_padding * 2; // restore initial width
1564 int rankings_rows = 0;
1565 int rankings_columns = 0;
1566 int rankings_cnt = 0;
1567 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1570 RANKINGS_RECEIVED_CNT = 0;
1571 for (i=RANKINGS_CNT-1; i>=0; --i)
1573 ++RANKINGS_RECEIVED_CNT;
1575 if (RANKINGS_RECEIVED_CNT == 0)
1578 vector hl_rgb = rgb + '0.5 0.5 0.5';
1580 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1581 pos.y += 1.25 * hud_fontsize.y;
1582 if(panel.current_panel_bg != "0")
1583 pos.y += panel_bg_border;
1588 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1590 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1595 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1597 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1601 float ranksize = 3 * hud_fontsize.x;
1602 float timesize = 5 * hud_fontsize.x;
1603 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1604 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1605 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1608 rankings_cnt = RANKINGS_RECEIVED_CNT;
1609 rankings_rows = ceil(rankings_cnt / rankings_columns);
1612 // expand name column to fill the entire row
1613 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1614 namesize += available_space;
1615 columnsize.x += available_space;
1617 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1618 panel_size.y += panel_bg_padding * 2;
1622 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1623 if(panel.current_panel_bg != "0")
1624 end_pos.y += panel_bg_border * 2;
1626 if(panel_bg_padding)
1628 panel_pos += '1 1 0' * panel_bg_padding;
1629 panel_size -= '2 2 0' * panel_bg_padding;
1635 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1637 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1639 int column = 0, j = 0;
1640 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1641 for(i = 0; i < rankings_cnt; ++i)
1648 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1649 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1650 else if(!((j + column) & 1) && sbt_highlight)
1651 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1653 str = count_ordinal(i+1);
1654 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1655 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1656 str = ColorTranslateRGB(grecordholder[i]);
1658 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1659 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1661 pos.y += 1.25 * hud_fontsize.y;
1663 if(j >= rankings_rows)
1667 pos.x += panel_size.x / rankings_columns;
1668 pos.y = panel_pos.y;
1671 strfree(zoned_name_self);
1673 panel_size.x += panel_bg_padding * 2; // restore initial width
1677 float scoreboard_time;
1678 bool have_weapon_stats;
1679 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1681 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1683 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1686 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1687 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1693 if (!have_weapon_stats)
1695 FOREACH(Weapons, it != WEP_Null, {
1696 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1697 if (weapon_stats >= 0)
1699 have_weapon_stats = true;
1703 if (!have_weapon_stats)
1710 bool have_item_stats;
1711 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1713 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1715 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1718 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1719 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1725 if (!have_item_stats)
1727 IL_EACH(default_order_items, true, {
1728 if (!is_item_filtered(it))
1730 int q = g_inventory.inv_items[it.m_id];
1731 //q = 1; // debug: display all items
1734 have_item_stats = true;
1739 if (!have_item_stats)
1746 vector Scoreboard_Spectators_Draw(vector pos) {
1751 for(pl = players.sort_next; pl; pl = pl.sort_next)
1753 if(pl.team == NUM_SPECTATOR)
1755 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1756 if(tm.team == NUM_SPECTATOR)
1758 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1759 draw_beginBoldFont();
1760 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1762 pos.y += 1.25 * hud_fontsize.y;
1764 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1765 pos.y += 1.25 * hud_fontsize.y;
1770 if (str != "") // if there's at least one spectator
1771 pos.y += 0.5 * hud_fontsize.y;
1776 void Scoreboard_Draw()
1778 if(!autocvar__hud_configure)
1780 if(!hud_draw_maximized) return;
1782 // frametime checks allow to toggle the scoreboard even when the game is paused
1783 if(scoreboard_active) {
1784 if (scoreboard_fade_alpha == 0)
1785 scoreboard_time = time;
1786 if(hud_configure_menu_open == 1)
1787 scoreboard_fade_alpha = 1;
1788 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1789 if (scoreboard_fadeinspeed && frametime)
1790 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1792 scoreboard_fade_alpha = 1;
1793 if(hud_fontsize_str != autocvar_hud_fontsize)
1795 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1796 Scoreboard_initFieldSizes();
1797 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1801 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1802 if (scoreboard_fadeoutspeed && frametime)
1803 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1805 scoreboard_fade_alpha = 0;
1808 if (!scoreboard_fade_alpha)
1810 scoreboard_acc_fade_alpha = 0;
1811 scoreboard_itemstats_fade_alpha = 0;
1816 scoreboard_fade_alpha = 0;
1818 if (autocvar_hud_panel_scoreboard_dynamichud)
1821 HUD_Scale_Disable();
1823 if(scoreboard_fade_alpha <= 0)
1825 panel_fade_alpha *= scoreboard_fade_alpha;
1826 HUD_Panel_LoadCvars();
1828 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1829 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1830 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1831 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1832 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1833 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1834 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1836 // don't overlap with con_notify
1837 if(!autocvar__hud_configure)
1838 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1840 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1841 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1842 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1843 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
1844 panel_pos.x = scoreboard_left;
1845 panel_size.x = fixed_scoreboard_width;
1847 Scoreboard_UpdatePlayerTeams();
1849 scoreboard_top = panel_pos.y;
1850 vector pos = panel_pos;
1855 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1857 // Begin of Game Info Section
1858 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1859 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1861 // Game Info: Game Type
1862 str = MapInfo_Type_ToText(gametype);
1863 draw_beginBoldFont();
1864 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);
1867 // Game Info: Game Detail
1868 float tl = STAT(TIMELIMIT);
1869 float fl = STAT(FRAGLIMIT);
1870 float ll = STAT(LEADLIMIT);
1871 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1874 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1875 if(!gametype.m_hidelimits)
1880 str = strcat(str, "^7 / "); // delimiter
1883 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1884 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1885 (teamscores_label(ts_primary) == "fastest") ? "" :
1886 TranslateScoresLabel(teamscores_label(ts_primary))));
1890 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1891 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1892 (scores_label(ps_primary) == "fastest") ? "" :
1893 TranslateScoresLabel(scores_label(ps_primary))));
1898 if(tl > 0 || fl > 0)
1901 if (ll_and_fl && fl > 0)
1902 str = strcat(str, "^7 & ");
1904 str = strcat(str, "^7 / ");
1909 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1910 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1911 (teamscores_label(ts_primary) == "fastest") ? "" :
1912 TranslateScoresLabel(teamscores_label(ts_primary))));
1916 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1917 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1918 (scores_label(ps_primary) == "fastest") ? "" :
1919 TranslateScoresLabel(scores_label(ps_primary))));
1924 pos.y += sb_gameinfo_type_fontsize.y;
1925 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
1927 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1928 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1929 // End of Game Info Section
1931 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1932 if(panel.current_panel_bg != "0")
1933 pos.y += panel_bg_border;
1935 // Draw the scoreboard
1936 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1939 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1943 vector panel_bg_color_save = panel_bg_color;
1944 vector team_score_baseoffset;
1945 vector team_size_baseoffset;
1946 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1948 // put team score to the left of scoreboard (and team size to the right)
1949 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1950 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1951 if(panel.current_panel_bg != "0")
1953 team_score_baseoffset.x -= panel_bg_border;
1954 team_size_baseoffset.x += panel_bg_border;
1959 // put team score to the right of scoreboard (and team size to the left)
1960 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1961 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1962 if(panel.current_panel_bg != "0")
1964 team_score_baseoffset.x += panel_bg_border;
1965 team_size_baseoffset.x -= panel_bg_border;
1969 int team_size_total = 0;
1970 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1972 // calculate team size total (sum of all team sizes)
1973 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1974 if(tm.team != NUM_SPECTATOR)
1975 team_size_total += tm.team_size;
1978 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1980 if(tm.team == NUM_SPECTATOR)
1985 draw_beginBoldFont();
1986 vector rgb = Team_ColorRGB(tm.team);
1987 str = ftos(tm.(teamscores(ts_primary)));
1988 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1990 // team score on the left (default)
1991 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1995 // team score on the right
1996 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1998 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2000 // team size (if set to show on the side)
2001 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2003 // calculate the starting position for the whole team size info string
2004 str = sprintf("%d/%d", tm.team_size, team_size_total);
2005 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2007 // team size on the left
2008 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2012 // team size on the right
2013 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2015 str = sprintf("%d", tm.team_size);
2016 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2017 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2018 str = sprintf("/%d", team_size_total);
2019 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2023 // secondary score, e.g. keyhunt
2024 if(ts_primary != ts_secondary)
2026 str = ftos(tm.(teamscores(ts_secondary)));
2027 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2030 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2035 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2038 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2041 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2042 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2043 else if(panel_bg_color_team > 0)
2044 panel_bg_color = rgb * panel_bg_color_team;
2046 panel_bg_color = rgb;
2047 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2049 panel_bg_color = panel_bg_color_save;
2053 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2054 if(tm.team != NUM_SPECTATOR)
2057 // display it anyway
2058 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2061 // draw scoreboard spectators before accuracy and item stats
2062 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2063 pos = Scoreboard_Spectators_Draw(pos);
2066 // draw accuracy and item stats
2067 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2068 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2069 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2070 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2072 // draw scoreboard spectators after accuracy and item stats and before rankings
2073 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2074 pos = Scoreboard_Spectators_Draw(pos);
2077 if(MUTATOR_CALLHOOK(ShowRankings)) {
2078 string ranktitle = M_ARGV(0, string);
2079 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2080 if(race_speedaward) {
2081 drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2082 pos.y += 1.25 * hud_fontsize.y;
2084 if(race_speedaward_alltimebest) {
2085 drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, ColorTranslateRGB(race_speedaward_alltimebest_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2086 pos.y += 1.25 * hud_fontsize.y;
2088 if (race_speedaward || race_speedaward_alltimebest)
2089 pos.y += 0.25 * hud_fontsize.y;
2090 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2095 // draw scoreboard spectators after rankings
2096 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2097 pos = Scoreboard_Spectators_Draw(pos);
2100 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2102 // draw scoreboard spectators after mapstats
2103 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2104 pos = Scoreboard_Spectators_Draw(pos);
2108 // print information about respawn status
2109 float respawn_time = STAT(RESPAWN_TIME);
2113 if(respawn_time < 0)
2115 // a negative number means we are awaiting respawn, time value is still the same
2116 respawn_time *= -1; // remove mark now that we checked it
2118 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2119 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2121 str = sprintf(_("^1Respawning in ^3%s^1..."),
2122 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2123 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2125 count_seconds(ceil(respawn_time - time))
2129 else if(time < respawn_time)
2131 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2132 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2133 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2135 count_seconds(ceil(respawn_time - time))
2139 else if(time >= respawn_time)
2140 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2142 pos.y += 1.2 * hud_fontsize.y;
2143 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2146 pos.y += hud_fontsize.y;
2147 if (scoreboard_fade_alpha < 1)
2148 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2149 else if (pos.y != scoreboard_bottom)
2151 if (pos.y > scoreboard_bottom)
2152 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2154 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2159 if (scoreboard_fade_alpha == 1)
2161 if (scoreboard_bottom > 0.95 * vid_conheight)
2162 rankings_rows = max(1, rankings_rows - 1);
2163 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2164 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2166 rankings_cnt = rankings_rows * rankings_columns;