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");
37 HUD_Write_Cvar("hud_panel_scoreboard_spectators_position");
40 const int MAX_SBT_FIELDS = MAX_SCORE;
42 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
43 float sbt_field_size[MAX_SBT_FIELDS + 1];
44 string sbt_field_title[MAX_SBT_FIELDS + 1];
47 string autocvar_hud_fontsize;
48 string hud_fontsize_str;
53 float sbt_fg_alpha_self;
55 float sbt_highlight_alpha;
56 float sbt_highlight_alpha_self;
57 float sbt_highlight_alpha_eliminated;
59 // provide basic panel cvars to old clients
60 // TODO remove them after a future release (0.8.2+)
61 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
62 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
63 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
64 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
65 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
66 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
67 noref string autocvar_hud_panel_scoreboard_bg_border = "";
68 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
70 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
71 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
72 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
73 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
74 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
75 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
76 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
77 bool autocvar_hud_panel_scoreboard_table_highlight = true;
78 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
79 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
80 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
81 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
82 float autocvar_hud_panel_scoreboard_namesize = 15;
83 float autocvar_hud_panel_scoreboard_team_size_position = 0;
84 float autocvar_hud_panel_scoreboard_spectators_position = 2;
86 bool autocvar_hud_panel_scoreboard_accuracy = true;
87 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
88 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
89 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
90 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
92 bool autocvar_hud_panel_scoreboard_itemstats = true;
93 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
94 bool autocvar_hud_panel_scoreboard_itemstats_filter = true;
95 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
96 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
98 bool autocvar_hud_panel_scoreboard_dynamichud = false;
100 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
101 bool autocvar_hud_panel_scoreboard_others_showscore = true;
102 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
103 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
104 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
105 bool autocvar_hud_panel_scoreboard_playerid = false;
106 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
107 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
109 // mode 0: returns translated label
110 // mode 1: prints name and description of all the labels
111 string Label_getInfo(string label, int mode)
114 label = "bckills"; // first case in the switch
118 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
119 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
120 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")));
121 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
122 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
123 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
124 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
125 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
126 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
127 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
128 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
129 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
130 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
131 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
132 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
133 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
134 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
135 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
136 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
137 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
138 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
139 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
140 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
141 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
142 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
143 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
144 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
145 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")));
146 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
147 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
148 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
149 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
150 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
151 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
152 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
153 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
154 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
155 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
156 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
157 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
158 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
159 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
160 default: return label;
165 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
166 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
168 #define SB_EXTRA_SORTING_FIELDS 5
169 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
170 void Scoreboard_InitScores()
174 ps_primary = ps_secondary = NULL;
175 ts_primary = ts_secondary = -1;
176 FOREACH(Scores, true, {
177 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
178 if(f == SFL_SORT_PRIO_PRIMARY)
180 if(f == SFL_SORT_PRIO_SECONDARY)
182 if(ps_primary == it || ps_secondary == it)
184 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
185 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
186 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
187 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
188 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
190 if(ps_secondary == NULL)
191 ps_secondary = ps_primary;
193 for(i = 0; i < MAX_TEAMSCORE; ++i)
195 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
196 if(f == SFL_SORT_PRIO_PRIMARY)
198 if(f == SFL_SORT_PRIO_SECONDARY)
201 if(ts_secondary == -1)
202 ts_secondary = ts_primary;
204 Cmd_Scoreboard_SetFields(0);
208 void Scoreboard_UpdatePlayerTeams()
212 for(pl = players.sort_next; pl; pl = pl.sort_next)
215 int Team = entcs_GetScoreTeam(pl.sv_entnum);
216 if(SetTeam(pl, Team))
219 Scoreboard_UpdatePlayerPos(pl);
223 pl = players.sort_next;
228 print(strcat("PNUM: ", ftos(num), "\n"));
233 int Scoreboard_CompareScore(int vl, int vr, int f)
235 TC(int, vl); TC(int, vr); TC(int, f);
236 if(f & SFL_ZERO_IS_WORST)
238 if(vl == 0 && vr != 0)
240 if(vl != 0 && vr == 0)
244 return IS_INCREASING(f);
246 return IS_DECREASING(f);
250 float Scoreboard_ComparePlayerScores(entity left, entity right)
252 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
253 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
260 if(vl == NUM_SPECTATOR)
262 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
264 if(!left.gotscores && right.gotscores)
271 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
275 if (!fld) fld = ps_primary;
276 else if (ps_secondary == ps_primary) continue;
277 else fld = ps_secondary;
281 fld = sb_extra_sorting_field[i];
282 if (fld == ps_primary || fld == ps_secondary) continue;
286 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
287 if (r >= 0) return r;
290 if (left.sv_entnum < right.sv_entnum)
296 void Scoreboard_UpdatePlayerPos(entity player)
299 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
301 SORT_SWAP(player, ent);
303 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
305 SORT_SWAP(ent, player);
309 float Scoreboard_CompareTeamScores(entity left, entity right)
311 if(left.team == NUM_SPECTATOR)
313 if(right.team == NUM_SPECTATOR)
318 for(int i = -2; i < MAX_TEAMSCORE; ++i)
322 if (fld_idx == -1) fld_idx = ts_primary;
323 else if (ts_secondary == ts_primary) continue;
324 else fld_idx = ts_secondary;
329 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
332 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
333 if (r >= 0) return r;
336 if (left.team < right.team)
342 void Scoreboard_UpdateTeamPos(entity Team)
345 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
347 SORT_SWAP(Team, ent);
349 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
351 SORT_SWAP(ent, Team);
355 void Cmd_Scoreboard_Help()
357 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
358 LOG_HELP(_("Usage:"));
359 LOG_HELP("^2scoreboard_columns_set ^3default");
360 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
361 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
362 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
363 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
364 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
365 LOG_HELP(_("The following field names are recognized (case insensitive):"));
371 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
372 "of game types, then a slash, to make the field show up only in these\n"
373 "or in all but these game types. You can also specify 'all' as a\n"
374 "field to show all fields available for the current game mode."));
377 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
378 "include/exclude ALL teams/noteams game modes."));
381 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
382 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
383 "right of the vertical bar aligned to the right."));
384 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
385 "other gamemodes except DM."));
388 // NOTE: adding a gametype with ? to not warn for an optional field
389 // make sure it's excluded in a previous exclusive rule, if any
390 // otherwise the previous exclusive rule warns anyway
391 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
392 #define SCOREBOARD_DEFAULT_COLUMNS \
393 "ping pl fps name |" \
394 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
395 " -teams,lms/deaths +ft,tdm/deaths" \
397 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
398 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
399 " +tdm,ft,dom,ons,as/teamkills"\
400 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
401 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
402 " +lms/lives +lms/rank" \
403 " +kh/kckills +kh/losses +kh/caps" \
404 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
405 " +as/objectives +nb/faults +nb/goals" \
406 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
407 " +dom/ticks +dom/takes" \
408 " -lms,rc,cts,inv,nb/score"
410 void Cmd_Scoreboard_SetFields(int argc)
415 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
419 return; // do nothing, we don't know gametype and scores yet
421 // sbt_fields uses strunzone on the titles!
422 if(!sbt_field_title[0])
423 for(i = 0; i < MAX_SBT_FIELDS; ++i)
424 sbt_field_title[i] = strzone("(null)");
426 // TODO: re enable with gametype dependant cvars?
427 if(argc < 3) // no arguments provided
428 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
431 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
435 if(argv(2) == "default" || argv(2) == "expand_default")
437 if(argv(2) == "expand_default")
438 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
439 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
441 else if(argv(2) == "all" || argv(2) == "ALL")
443 string s = "ping pl name |"; // scores without label (not really scores)
446 // scores without label
447 s = strcat(s, " ", "sum");
448 s = strcat(s, " ", "kdratio");
449 s = strcat(s, " ", "frags");
451 FOREACH(Scores, true, {
453 if(it != ps_secondary)
454 if(scores_label(it) != "")
455 s = strcat(s, " ", scores_label(it));
457 if(ps_secondary != ps_primary)
458 s = strcat(s, " ", scores_label(ps_secondary));
459 s = strcat(s, " ", scores_label(ps_primary));
460 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
467 hud_fontsize = HUD_GetFontsize("hud_fontsize");
469 for(i = 1; i < argc - 1; ++i)
472 bool nocomplain = false;
473 if(substring(str, 0, 1) == "?")
476 str = substring(str, 1, strlen(str) - 1);
479 slash = strstrofs(str, "/", 0);
482 pattern = substring(str, 0, slash);
483 str = substring(str, slash + 1, strlen(str) - (slash + 1));
485 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
489 str = strtolower(str);
490 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
491 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
496 // fields without a label (not networked via the score system)
497 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
498 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
499 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
500 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
501 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
502 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
503 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
504 default: // fields with a label
506 // map alternative labels
507 if (str == "damage") str = "dmg";
508 if (str == "damagetaken") str = "dmgtaken";
510 FOREACH(Scores, true, {
511 if (str == strtolower(scores_label(it))) {
513 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
517 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
518 if(!nocomplain && str != "fps") // server can disable the fps field
519 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
521 strfree(sbt_field_title[sbt_num_fields]);
522 sbt_field_size[sbt_num_fields] = 0;
526 sbt_field[sbt_num_fields] = j;
529 if(j == ps_secondary)
530 have_secondary = true;
535 if(sbt_num_fields >= MAX_SBT_FIELDS)
539 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
541 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
542 have_secondary = true;
543 if(ps_primary == ps_secondary)
544 have_secondary = true;
545 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
547 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
551 strfree(sbt_field_title[sbt_num_fields]);
552 for(i = sbt_num_fields; i > 0; --i)
554 sbt_field_title[i] = sbt_field_title[i-1];
555 sbt_field_size[i] = sbt_field_size[i-1];
556 sbt_field[i] = sbt_field[i-1];
558 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
559 sbt_field[0] = SP_NAME;
561 LOG_INFO("fixed missing field 'name'");
565 strfree(sbt_field_title[sbt_num_fields]);
566 for(i = sbt_num_fields; i > 1; --i)
568 sbt_field_title[i] = sbt_field_title[i-1];
569 sbt_field_size[i] = sbt_field_size[i-1];
570 sbt_field[i] = sbt_field[i-1];
572 sbt_field_title[1] = strzone("|");
573 sbt_field[1] = SP_SEPARATOR;
574 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
576 LOG_INFO("fixed missing field '|'");
579 else if(!have_separator)
581 strcpy(sbt_field_title[sbt_num_fields], "|");
582 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
583 sbt_field[sbt_num_fields] = SP_SEPARATOR;
585 LOG_INFO("fixed missing field '|'");
589 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
590 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
591 sbt_field[sbt_num_fields] = ps_secondary;
593 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
597 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
598 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
599 sbt_field[sbt_num_fields] = ps_primary;
601 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
605 sbt_field[sbt_num_fields] = SP_END;
608 string Scoreboard_AddPlayerId(string pl_name, entity pl)
610 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
611 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
612 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
616 vector sbt_field_rgb;
617 string sbt_field_icon0;
618 string sbt_field_icon1;
619 string sbt_field_icon2;
620 vector sbt_field_icon0_rgb;
621 vector sbt_field_icon1_rgb;
622 vector sbt_field_icon2_rgb;
623 string Scoreboard_GetName(entity pl)
625 if(ready_waiting && pl.ready)
627 sbt_field_icon0 = "gfx/scoreboard/player_ready";
631 int f = entcs_GetClientColors(pl.sv_entnum);
633 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
634 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
635 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
636 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
637 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
640 return entcs_GetName(pl.sv_entnum);
643 string Scoreboard_GetField(entity pl, PlayerScoreField field)
645 float tmp, num, denom;
648 sbt_field_rgb = '1 1 1';
649 sbt_field_icon0 = "";
650 sbt_field_icon1 = "";
651 sbt_field_icon2 = "";
652 sbt_field_icon0_rgb = '1 1 1';
653 sbt_field_icon1_rgb = '1 1 1';
654 sbt_field_icon2_rgb = '1 1 1';
659 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
660 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
664 tmp = max(0, min(220, f-80)) / 220;
665 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
671 f = pl.ping_packetloss;
672 tmp = pl.ping_movementloss;
673 if(f == 0 && tmp == 0)
675 str = ftos(ceil(f * 100));
677 str = strcat(str, "~", ftos(ceil(tmp * 100)));
678 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
679 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
683 str = Scoreboard_GetName(pl);
684 if (autocvar_hud_panel_scoreboard_playerid)
685 str = Scoreboard_AddPlayerId(str, pl);
689 f = pl.(scores(SP_KILLS));
690 f -= pl.(scores(SP_SUICIDES));
694 num = pl.(scores(SP_KILLS));
695 denom = pl.(scores(SP_DEATHS));
698 sbt_field_rgb = '0 1 0';
699 str = sprintf("%d", num);
700 } else if(num <= 0) {
701 sbt_field_rgb = '1 0 0';
702 str = sprintf("%.1f", num/denom);
704 str = sprintf("%.1f", num/denom);
708 f = pl.(scores(SP_KILLS));
709 f -= pl.(scores(SP_DEATHS));
712 sbt_field_rgb = '0 1 0';
714 sbt_field_rgb = '1 1 1';
716 sbt_field_rgb = '1 0 0';
722 float elo = pl.(scores(SP_ELO));
724 case -1: return "...";
725 case -2: return _("N/A");
726 default: return ftos(elo);
732 float fps = pl.(scores(SP_FPS));
735 sbt_field_rgb = '1 1 1';
736 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
738 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
739 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
743 case SP_DMG: case SP_DMGTAKEN:
744 return sprintf("%.1f k", pl.(scores(field)) / 1000);
746 default: case SP_SCORE:
747 tmp = pl.(scores(field));
748 f = scores_flags(field);
749 if(field == ps_primary)
750 sbt_field_rgb = '1 1 0';
751 else if(field == ps_secondary)
752 sbt_field_rgb = '0 1 1';
754 sbt_field_rgb = '1 1 1';
755 return ScoreString(f, tmp);
760 float sbt_fixcolumnwidth_len;
761 float sbt_fixcolumnwidth_iconlen;
762 float sbt_fixcolumnwidth_marginlen;
764 string Scoreboard_FixColumnWidth(int i, string str)
770 sbt_fixcolumnwidth_iconlen = 0;
772 if(sbt_field_icon0 != "")
774 sz = draw_getimagesize(sbt_field_icon0);
776 if(sbt_fixcolumnwidth_iconlen < f)
777 sbt_fixcolumnwidth_iconlen = f;
780 if(sbt_field_icon1 != "")
782 sz = draw_getimagesize(sbt_field_icon1);
784 if(sbt_fixcolumnwidth_iconlen < f)
785 sbt_fixcolumnwidth_iconlen = f;
788 if(sbt_field_icon2 != "")
790 sz = draw_getimagesize(sbt_field_icon2);
792 if(sbt_fixcolumnwidth_iconlen < f)
793 sbt_fixcolumnwidth_iconlen = f;
796 if(sbt_fixcolumnwidth_iconlen != 0)
798 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
799 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
802 sbt_fixcolumnwidth_marginlen = 0;
804 if(sbt_field[i] == SP_NAME) // name gets all remaining space
807 float remaining_space = 0;
808 for(j = 0; j < sbt_num_fields; ++j)
810 if (sbt_field[i] != SP_SEPARATOR)
811 remaining_space += sbt_field_size[j] + hud_fontsize.x;
812 sbt_field_size[i] = panel_size.x - remaining_space;
814 if (sbt_fixcolumnwidth_iconlen != 0)
815 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
816 float namesize = panel_size.x - remaining_space;
817 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
818 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
820 max_namesize = vid_conwidth - remaining_space;
823 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
825 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
826 if(sbt_field_size[i] < f)
827 sbt_field_size[i] = f;
832 void Scoreboard_initFieldSizes()
834 for(int i = 0; i < sbt_num_fields; ++i)
836 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
837 Scoreboard_FixColumnWidth(i, "");
841 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
844 vector column_dim = eY * panel_size.y;
846 column_dim.y -= 1.25 * hud_fontsize.y;
847 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
848 pos.x += hud_fontsize.x * 0.5;
849 for(i = 0; i < sbt_num_fields; ++i)
851 if(sbt_field[i] == SP_SEPARATOR)
853 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
856 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
857 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
858 pos.x += column_dim.x;
860 if(sbt_field[i] == SP_SEPARATOR)
862 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
863 for(i = sbt_num_fields - 1; i > 0; --i)
865 if(sbt_field[i] == SP_SEPARATOR)
868 pos.x -= sbt_field_size[i];
873 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
874 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
877 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
878 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
879 pos.x -= hud_fontsize.x;
884 pos.y += 1.25 * hud_fontsize.y;
888 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
890 TC(bool, is_self); TC(int, pl_number);
892 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
894 vector h_pos = item_pos;
895 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
896 // alternated rows highlighting
898 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
899 else if((sbt_highlight) && (!(pl_number % 2)))
900 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
902 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
904 vector pos = item_pos;
905 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
907 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
909 pos.x += hud_fontsize.x * 0.5;
910 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
911 vector tmp = '0 0 0';
913 PlayerScoreField field;
914 for(i = 0; i < sbt_num_fields; ++i)
916 field = sbt_field[i];
917 if(field == SP_SEPARATOR)
920 if(is_spec && field != SP_NAME && field != SP_PING) {
921 pos.x += sbt_field_size[i] + hud_fontsize.x;
924 str = Scoreboard_GetField(pl, field);
925 str = Scoreboard_FixColumnWidth(i, str);
927 pos.x += sbt_field_size[i] + hud_fontsize.x;
929 if(field == SP_NAME) {
930 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
931 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
933 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
934 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
937 tmp.x = sbt_field_size[i] + hud_fontsize.x;
938 if(sbt_field_icon0 != "")
939 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
940 if(sbt_field_icon1 != "")
941 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
942 if(sbt_field_icon2 != "")
943 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
946 if(sbt_field[i] == SP_SEPARATOR)
948 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
949 for(i = sbt_num_fields-1; i > 0; --i)
951 field = sbt_field[i];
952 if(field == SP_SEPARATOR)
955 if(is_spec && field != SP_NAME && field != SP_PING) {
956 pos.x -= sbt_field_size[i] + hud_fontsize.x;
960 str = Scoreboard_GetField(pl, field);
961 str = Scoreboard_FixColumnWidth(i, str);
963 if(field == SP_NAME) {
964 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
965 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
967 tmp.x = sbt_fixcolumnwidth_len;
968 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
971 tmp.x = sbt_field_size[i];
972 if(sbt_field_icon0 != "")
973 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
974 if(sbt_field_icon1 != "")
975 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
976 if(sbt_field_icon2 != "")
977 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
978 pos.x -= sbt_field_size[i] + hud_fontsize.x;
983 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
986 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
989 vector h_pos = item_pos;
990 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
992 bool complete = (this_team == NUM_SPECTATOR);
995 if((sbt_highlight) && (!(pl_number % 2)))
996 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
998 vector pos = item_pos;
999 pos.x += hud_fontsize.x * 0.5;
1000 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1002 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1004 width_limit -= stringwidth("...", false, hud_fontsize);
1005 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1006 static float max_name_width = 0;
1008 float fieldsize = 0;
1009 float min_fieldsize = 0;
1010 float fieldpadding = hud_fontsize.x * 0.25;
1011 if(this_team == NUM_SPECTATOR)
1013 if(autocvar_hud_panel_scoreboard_spectators_showping)
1014 min_fieldsize = stringwidth("999", false, hud_fontsize);
1016 else if(autocvar_hud_panel_scoreboard_others_showscore)
1017 min_fieldsize = stringwidth("99", false, hud_fontsize);
1018 for(i = 0; pl; pl = pl.sort_next)
1020 if(pl.team != this_team)
1022 if(pl == ignored_pl)
1026 if(this_team == NUM_SPECTATOR)
1028 if(autocvar_hud_panel_scoreboard_spectators_showping)
1029 field = Scoreboard_GetField(pl, SP_PING);
1031 else if(autocvar_hud_panel_scoreboard_others_showscore)
1032 field = Scoreboard_GetField(pl, SP_SCORE);
1034 string str = entcs_GetName(pl.sv_entnum);
1035 if (autocvar_hud_panel_scoreboard_playerid)
1036 str = Scoreboard_AddPlayerId(str, pl);
1037 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1038 float column_width = stringwidth(str, true, hud_fontsize);
1039 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1041 if(column_width > max_name_width)
1042 max_name_width = column_width;
1043 column_width = max_name_width;
1047 fieldsize = stringwidth(field, false, hud_fontsize);
1048 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1051 if(pos.x + column_width > width_limit)
1056 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1061 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1062 pos.y += hud_fontsize.y * 1.25;
1066 vector name_pos = pos;
1067 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1068 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1069 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1072 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1073 h_size.y = hud_fontsize.y;
1074 vector field_pos = pos;
1075 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1076 field_pos.x += column_width - h_size.x;
1078 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1079 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1080 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1084 h_size.x = column_width + hud_fontsize.x * 0.25;
1085 h_size.y = hud_fontsize.y;
1086 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1088 pos.x += column_width;
1089 pos.x += hud_fontsize.x;
1091 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1094 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1096 int max_players = 999;
1097 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1099 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1102 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1103 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1104 height /= team_count;
1107 height -= panel_bg_padding * 2; // - padding
1108 max_players = floor(height / (hud_fontsize.y * 1.25));
1109 if(max_players <= 1)
1111 if(max_players == tm.team_size)
1116 entity me = playerslots[current_player];
1118 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1119 panel_size.y += panel_bg_padding * 2;
1122 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1123 if(panel.current_panel_bg != "0")
1124 end_pos.y += panel_bg_border * 2;
1126 if(panel_bg_padding)
1128 panel_pos += '1 1 0' * panel_bg_padding;
1129 panel_size -= '2 2 0' * panel_bg_padding;
1133 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1137 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1139 pos.y += 1.25 * hud_fontsize.y;
1142 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1144 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1147 // print header row and highlight columns
1148 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1150 // fill the table and draw the rows
1151 bool is_self = false;
1152 bool self_shown = false;
1154 for(pl = players.sort_next; pl; pl = pl.sort_next)
1156 if(pl.team != tm.team)
1158 if(i == max_players - 2 && pl != me)
1160 if(!self_shown && me.team == tm.team)
1162 Scoreboard_DrawItem(pos, rgb, me, true, i);
1164 pos.y += 1.25 * hud_fontsize.y;
1168 if(i >= max_players - 1)
1170 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1173 is_self = (pl.sv_entnum == current_player);
1174 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1177 pos.y += 1.25 * hud_fontsize.y;
1181 panel_size.x += panel_bg_padding * 2; // restore initial width
1185 bool Scoreboard_WouldDraw()
1187 if (MUTATOR_CALLHOOK(DrawScoreboard))
1189 else if (QuickMenu_IsOpened())
1191 else if (HUD_Radar_Clickable())
1193 else if (scoreboard_showscores)
1195 else if (intermission == 1)
1197 else if (intermission == 2)
1199 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1200 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1204 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1209 float average_accuracy;
1210 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1212 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1214 WepSet weapons_stat = WepSet_GetFromStat();
1215 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1216 int disownedcnt = 0;
1218 FOREACH(Weapons, it != WEP_Null, {
1219 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1221 WepSet set = it.m_wepset;
1222 if(it.spawnflags & WEP_TYPE_OTHER)
1227 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1229 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1236 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1237 if (weapon_cnt <= 0) return pos;
1240 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1242 int columnns = ceil(weapon_cnt / rows);
1244 float weapon_height = 29;
1245 float height = hud_fontsize.y + weapon_height;
1247 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);
1248 pos.y += 1.25 * hud_fontsize.y;
1249 if(panel.current_panel_bg != "0")
1250 pos.y += panel_bg_border;
1253 panel_size.y = height * rows;
1254 panel_size.y += panel_bg_padding * 2;
1256 float panel_bg_alpha_save = panel_bg_alpha;
1257 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1259 panel_bg_alpha = panel_bg_alpha_save;
1261 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1262 if(panel.current_panel_bg != "0")
1263 end_pos.y += panel_bg_border * 2;
1265 if(panel_bg_padding)
1267 panel_pos += '1 1 0' * panel_bg_padding;
1268 panel_size -= '2 2 0' * panel_bg_padding;
1272 vector tmp = panel_size;
1274 float weapon_width = tmp.x / columnns / rows;
1277 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1281 // column highlighting
1282 for (int i = 0; i < columnns; ++i)
1284 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);
1287 for (int i = 0; i < rows; ++i)
1288 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1291 average_accuracy = 0;
1292 int weapons_with_stats = 0;
1294 pos.x += weapon_width / 2;
1296 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1299 Accuracy_LoadColors();
1301 float oldposx = pos.x;
1305 FOREACH(Weapons, it != WEP_Null, {
1306 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1308 WepSet set = it.m_wepset;
1309 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1311 if (it.spawnflags & WEP_TYPE_OTHER)
1315 if (weapon_stats >= 0)
1316 weapon_alpha = sbt_fg_alpha;
1318 weapon_alpha = 0.2 * sbt_fg_alpha;
1321 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1323 if (weapon_stats >= 0) {
1324 weapons_with_stats += 1;
1325 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1328 s = sprintf("%d%%", weapon_stats * 100);
1331 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1333 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1334 rgb = Accuracy_GetColor(weapon_stats);
1336 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1338 tmpos.x += weapon_width * rows;
1339 pos.x += weapon_width * rows;
1340 if (rows == 2 && column == columnns - 1) {
1348 if (weapons_with_stats)
1349 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1351 panel_size.x += panel_bg_padding * 2; // restore initial width
1356 .bool uninteresting;
1357 STATIC_INIT(default_order_items_label)
1359 IL_EACH(default_order_items, true, {
1360 if(!(it.instanceOfPowerup
1361 || it == ITEM_HealthMega || it == ITEM_HealthBig
1362 || it == ITEM_ArmorMega || it == ITEM_ArmorBig
1365 it.uninteresting = true;
1370 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1372 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1374 int disowned_cnt = 0;
1375 int uninteresting_cnt = 0;
1376 IL_EACH(default_order_items, true, {
1377 int q = g_inventory.inv_items[it.m_id];
1378 //q = 1; // debug: display all items
1379 if (autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting)
1380 ++uninteresting_cnt;
1384 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1385 int n = items_cnt - disowned_cnt;
1386 if (n <= 0) return pos;
1388 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1389 int columnns = max(6, ceil(n / rows));
1392 float fontsize = height * 1/3;
1393 float item_height = height * 2/3;
1395 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1396 pos.y += 1.25 * hud_fontsize.y;
1397 if(panel.current_panel_bg != "0")
1398 pos.y += panel_bg_border;
1401 panel_size.y = height * rows;
1402 panel_size.y += panel_bg_padding * 2;
1404 float panel_bg_alpha_save = panel_bg_alpha;
1405 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1407 panel_bg_alpha = panel_bg_alpha_save;
1409 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1410 if(panel.current_panel_bg != "0")
1411 end_pos.y += panel_bg_border * 2;
1413 if(panel_bg_padding)
1415 panel_pos += '1 1 0' * panel_bg_padding;
1416 panel_size -= '2 2 0' * panel_bg_padding;
1420 vector tmp = panel_size;
1422 float item_width = tmp.x / columnns / rows;
1425 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1429 // column highlighting
1430 for (int i = 0; i < columnns; ++i)
1432 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);
1435 for (int i = 0; i < rows; ++i)
1436 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);
1440 pos.x += item_width / 2;
1442 float oldposx = pos.x;
1446 IL_EACH(default_order_items, !(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting), {
1447 int n = g_inventory.inv_items[it.m_id];
1448 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1449 if (n <= 0) continue;
1450 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);
1452 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1453 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);
1454 tmpos.x += item_width * rows;
1455 pos.x += item_width * rows;
1456 if (rows == 2 && column == columnns - 1) {
1464 panel_size.x += panel_bg_padding * 2; // restore initial width
1469 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1471 pos.x += hud_fontsize.x * 0.25;
1472 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1473 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1474 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1476 pos.y += hud_fontsize.y;
1481 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1482 float stat_secrets_found, stat_secrets_total;
1483 float stat_monsters_killed, stat_monsters_total;
1487 // get monster stats
1488 stat_monsters_killed = STAT(MONSTERS_KILLED);
1489 stat_monsters_total = STAT(MONSTERS_TOTAL);
1491 // get secrets stats
1492 stat_secrets_found = STAT(SECRETS_FOUND);
1493 stat_secrets_total = STAT(SECRETS_TOTAL);
1495 // get number of rows
1496 if(stat_secrets_total)
1498 if(stat_monsters_total)
1501 // if no rows, return
1505 // draw table header
1506 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1507 pos.y += 1.25 * hud_fontsize.y;
1508 if(panel.current_panel_bg != "0")
1509 pos.y += panel_bg_border;
1512 panel_size.y = hud_fontsize.y * rows;
1513 panel_size.y += panel_bg_padding * 2;
1516 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1517 if(panel.current_panel_bg != "0")
1518 end_pos.y += panel_bg_border * 2;
1520 if(panel_bg_padding)
1522 panel_pos += '1 1 0' * panel_bg_padding;
1523 panel_size -= '2 2 0' * panel_bg_padding;
1527 vector tmp = panel_size;
1530 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1533 if(stat_monsters_total)
1535 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1536 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1540 if(stat_secrets_total)
1542 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1543 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1546 panel_size.x += panel_bg_padding * 2; // restore initial width
1551 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1554 RANKINGS_RECEIVED_CNT = 0;
1555 for (i=RANKINGS_CNT-1; i>=0; --i)
1557 ++RANKINGS_RECEIVED_CNT;
1559 if (RANKINGS_RECEIVED_CNT == 0)
1562 vector hl_rgb = rgb + '0.5 0.5 0.5';
1564 pos.y += hud_fontsize.y;
1565 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1566 pos.y += 1.25 * hud_fontsize.y;
1567 if(panel.current_panel_bg != "0")
1568 pos.y += panel_bg_border;
1573 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1575 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1580 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1582 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1586 float ranksize = 3 * hud_fontsize.x;
1587 float timesize = 5 * hud_fontsize.x;
1588 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1589 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1590 columns = min(columns, RANKINGS_RECEIVED_CNT);
1592 // expand name column to fill the entire row
1593 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1594 namesize += available_space;
1595 columnsize.x += available_space;
1597 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1598 panel_size.y += panel_bg_padding * 2;
1602 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1603 if(panel.current_panel_bg != "0")
1604 end_pos.y += panel_bg_border * 2;
1606 if(panel_bg_padding)
1608 panel_pos += '1 1 0' * panel_bg_padding;
1609 panel_size -= '2 2 0' * panel_bg_padding;
1615 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1617 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1619 int column = 0, j = 0;
1620 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1621 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1628 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1629 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1630 else if(!((j + column) & 1) && sbt_highlight)
1631 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1633 str = count_ordinal(i+1);
1634 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1635 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1636 str = ColorTranslateRGB(grecordholder[i]);
1638 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1639 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1641 pos.y += 1.25 * hud_fontsize.y;
1643 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1647 pos.x += panel_size.x / columns;
1648 pos.y = panel_pos.y;
1651 strfree(zoned_name_self);
1653 panel_size.x += panel_bg_padding * 2; // restore initial width
1657 float scoreboard_time;
1658 bool have_weapon_stats;
1659 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1661 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1663 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1666 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1667 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1673 if (!have_weapon_stats)
1675 FOREACH(Weapons, it != WEP_Null, {
1676 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1677 if (weapon_stats >= 0)
1679 have_weapon_stats = true;
1683 if (!have_weapon_stats)
1690 bool have_item_stats;
1691 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1693 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1695 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1698 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1699 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1705 if (!have_item_stats)
1707 IL_EACH(default_order_items, true, {
1708 if (!(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting))
1710 int q = g_inventory.inv_items[it.m_id];
1711 //q = 1; // debug: display all items
1714 have_item_stats = true;
1719 if (!have_item_stats)
1726 vector Scoreboard_Spectators_Draw(vector pos, entity tm, string str, vector hud_fontsize) {
1730 for(pl = players.sort_next; pl; pl = pl.sort_next)
1732 if(pl.team == NUM_SPECTATOR)
1734 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1735 if(tm.team == NUM_SPECTATOR)
1737 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1738 draw_beginBoldFont();
1739 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1741 pos.y += 1.25 * hud_fontsize.y;
1743 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1744 pos.y += 1.25 * hud_fontsize.y;
1753 void Scoreboard_Draw()
1755 if(!autocvar__hud_configure)
1757 if(!hud_draw_maximized) return;
1759 // frametime checks allow to toggle the scoreboard even when the game is paused
1760 if(scoreboard_active) {
1761 if (scoreboard_fade_alpha == 0)
1762 scoreboard_time = time;
1763 if(hud_configure_menu_open == 1)
1764 scoreboard_fade_alpha = 1;
1765 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1766 if (scoreboard_fadeinspeed && frametime)
1767 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1769 scoreboard_fade_alpha = 1;
1770 if(hud_fontsize_str != autocvar_hud_fontsize)
1772 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1773 Scoreboard_initFieldSizes();
1774 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1778 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1779 if (scoreboard_fadeoutspeed && frametime)
1780 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1782 scoreboard_fade_alpha = 0;
1785 if (!scoreboard_fade_alpha)
1787 scoreboard_acc_fade_alpha = 0;
1788 scoreboard_itemstats_fade_alpha = 0;
1793 scoreboard_fade_alpha = 0;
1795 if (autocvar_hud_panel_scoreboard_dynamichud)
1798 HUD_Scale_Disable();
1800 if(scoreboard_fade_alpha <= 0)
1802 panel_fade_alpha *= scoreboard_fade_alpha;
1803 HUD_Panel_LoadCvars();
1805 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1806 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1807 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1808 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1809 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1810 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1811 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1813 // don't overlap with con_notify
1814 if(!autocvar__hud_configure)
1815 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1817 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1818 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1819 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1820 panel_size.x = fixed_scoreboard_width;
1822 Scoreboard_UpdatePlayerTeams();
1824 float initial_pos_y = panel_pos.y;
1825 vector pos = panel_pos;
1830 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1832 // Begin of Game Info Section
1833 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1834 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1836 // Game Info: Game Type
1837 str = MapInfo_Type_ToText(gametype);
1838 draw_beginBoldFont();
1839 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);
1842 // Game Info: Game Detail
1843 float tl = STAT(TIMELIMIT);
1844 float fl = STAT(FRAGLIMIT);
1845 float ll = STAT(LEADLIMIT);
1846 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1849 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1850 if(!gametype.m_hidelimits)
1855 str = strcat(str, "^7 / "); // delimiter
1858 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1859 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1860 (teamscores_label(ts_primary) == "fastest") ? "" :
1861 TranslateScoresLabel(teamscores_label(ts_primary))));
1865 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1866 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1867 (scores_label(ps_primary) == "fastest") ? "" :
1868 TranslateScoresLabel(scores_label(ps_primary))));
1873 if(tl > 0 || fl > 0)
1876 if (ll_and_fl && fl > 0)
1877 str = strcat(str, "^7 & ");
1879 str = strcat(str, "^7 / ");
1884 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1885 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1886 (teamscores_label(ts_primary) == "fastest") ? "" :
1887 TranslateScoresLabel(teamscores_label(ts_primary))));
1891 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1892 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1893 (scores_label(ps_primary) == "fastest") ? "" :
1894 TranslateScoresLabel(scores_label(ps_primary))));
1899 pos.y += sb_gameinfo_type_fontsize.y;
1900 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
1902 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1903 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1904 // End of Game Info Section
1906 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1907 if(panel.current_panel_bg != "0")
1908 pos.y += panel_bg_border;
1910 // Draw the scoreboard
1911 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1914 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1918 vector panel_bg_color_save = panel_bg_color;
1919 vector team_score_baseoffset;
1920 vector team_size_baseoffset;
1921 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1923 // put team score to the left of scoreboard (and team size to the right)
1924 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1925 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1926 if(panel.current_panel_bg != "0")
1928 team_score_baseoffset.x -= panel_bg_border;
1929 team_size_baseoffset.x += panel_bg_border;
1934 // put team score to the right of scoreboard (and team size to the left)
1935 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1936 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1937 if(panel.current_panel_bg != "0")
1939 team_score_baseoffset.x += panel_bg_border;
1940 team_size_baseoffset.x -= panel_bg_border;
1944 int team_size_total = 0;
1945 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1947 // calculate team size total (sum of all team sizes)
1948 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1949 if(tm.team != NUM_SPECTATOR)
1950 team_size_total += tm.team_size;
1953 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1955 if(tm.team == NUM_SPECTATOR)
1960 draw_beginBoldFont();
1961 vector rgb = Team_ColorRGB(tm.team);
1962 str = ftos(tm.(teamscores(ts_primary)));
1963 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1965 // team score on the left (default)
1966 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1970 // team score on the right
1971 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1973 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1975 // team size (if set to show on the side)
1976 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1978 // calculate the starting position for the whole team size info string
1979 str = sprintf("%d/%d", tm.team_size, team_size_total);
1980 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1982 // team size on the left
1983 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1987 // team size on the right
1988 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1990 str = sprintf("%d", tm.team_size);
1991 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1992 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1993 str = sprintf("/%d", team_size_total);
1994 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1998 // secondary score, e.g. keyhunt
1999 if(ts_primary != ts_secondary)
2001 str = ftos(tm.(teamscores(ts_secondary)));
2002 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2005 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2010 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2013 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2016 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2017 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2018 else if(panel_bg_color_team > 0)
2019 panel_bg_color = rgb * panel_bg_color_team;
2021 panel_bg_color = rgb;
2022 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2024 panel_bg_color = panel_bg_color_save;
2028 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2029 if(tm.team != NUM_SPECTATOR)
2032 // display it anyway
2033 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2036 // draw scoreboard spectators before accuracy and item stats
2037 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2038 pos = Scoreboard_Spectators_Draw(pos, tm, str, hud_fontsize);
2041 // draw accuracy and item stats
2042 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2043 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2044 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2045 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2047 // draw scoreboard spectators after accuracy and item stats and before rankings
2048 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2049 pos = Scoreboard_Spectators_Draw(pos, tm, str, hud_fontsize);
2052 if(MUTATOR_CALLHOOK(ShowRankings)) {
2053 string ranktitle = M_ARGV(0, string);
2054 if(race_speedaward) {
2055 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);
2056 pos.y += 1.25 * hud_fontsize.y;
2058 if(race_speedaward_alltimebest) {
2059 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);
2060 pos.y += 1.25 * hud_fontsize.y;
2062 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2065 // draw scoreboard spectators after rankings
2066 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2067 pos = Scoreboard_Spectators_Draw(pos, tm, str, hud_fontsize);
2070 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2072 // draw scoreboard spectators after mapstats
2073 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2074 pos = Scoreboard_Spectators_Draw(pos, tm, str, hud_fontsize);
2078 // print information about respawn status
2079 float respawn_time = STAT(RESPAWN_TIME);
2083 if(respawn_time < 0)
2085 // a negative number means we are awaiting respawn, time value is still the same
2086 respawn_time *= -1; // remove mark now that we checked it
2088 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2089 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2091 str = sprintf(_("^1Respawning in ^3%s^1..."),
2092 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2093 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2095 count_seconds(ceil(respawn_time - time))
2099 else if(time < respawn_time)
2101 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2102 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2103 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2105 count_seconds(ceil(respawn_time - time))
2109 else if(time >= respawn_time)
2110 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2112 pos.y += 1.2 * hud_fontsize.y;
2113 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2116 pos.y += 2 * hud_fontsize.y;
2117 if (scoreboard_fade_alpha < 1)
2118 scoreboard_bottom = initial_pos_y + (pos.y - initial_pos_y) * scoreboard_fade_alpha;
2119 else if (pos.y != scoreboard_bottom)
2121 if (pos.y > scoreboard_bottom)
2122 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - initial_pos_y));
2124 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - initial_pos_y));