1 #include "scoreboard.qh"
3 #include <client/draw.qh>
4 #include <client/hud/panel/chat.qh>
5 #include <client/hud/panel/quickmenu.qh>
6 #include <client/hud/panel/racetimer.qh>
7 #include <client/hud/panel/weapons.qh>
8 #include <common/constants.qh>
9 #include <common/ent_cs.qh>
10 #include <common/mapinfo.qh>
11 #include <common/minigames/cl_minigames.qh>
12 #include <common/net_linked.qh>
13 #include <common/scores.qh>
14 #include <common/stats.qh>
15 #include <common/teams.qh>
16 #include <common/items/inventory.qh>
20 void Scoreboard_Draw_Export(int fh)
22 // allow saving cvars that aesthetically change the panel into hud skin files
23 HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
24 HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
25 HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
26 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
27 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
28 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
29 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
30 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
31 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
32 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
33 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_eliminated");
34 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
35 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
36 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
39 const int MAX_SBT_FIELDS = MAX_SCORE;
41 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
42 float sbt_field_size[MAX_SBT_FIELDS + 1];
43 string sbt_field_title[MAX_SBT_FIELDS + 1];
46 string autocvar_hud_fontsize;
47 string hud_fontsize_str;
52 float sbt_fg_alpha_self;
54 float sbt_highlight_alpha;
55 float sbt_highlight_alpha_self;
56 float sbt_highlight_alpha_eliminated;
58 // provide basic panel cvars to old clients
59 // TODO remove them after a future release (0.8.2+)
60 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
61 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
62 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
63 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
64 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
65 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
66 noref string autocvar_hud_panel_scoreboard_bg_border = "";
67 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
69 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
70 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
71 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
72 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
73 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
74 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
75 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
76 bool autocvar_hud_panel_scoreboard_table_highlight = true;
77 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
78 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
79 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
80 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
81 float autocvar_hud_panel_scoreboard_namesize = 15;
82 float autocvar_hud_panel_scoreboard_team_size_position = 0;
84 bool autocvar_hud_panel_scoreboard_accuracy = true;
85 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
86 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
87 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
88 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
90 bool autocvar_hud_panel_scoreboard_itemstats = true;
91 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
92 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
93 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
94 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
95 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
97 bool autocvar_hud_panel_scoreboard_dynamichud = false;
99 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
100 bool autocvar_hud_panel_scoreboard_others_showscore = true;
101 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
102 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
103 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
104 bool autocvar_hud_panel_scoreboard_playerid = false;
105 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
106 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
108 // mode 0: returns translated label
109 // mode 1: prints name and description of all the labels
110 string Label_getInfo(string label, int mode)
113 label = "bckills"; // first case in the switch
117 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
118 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
119 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")));
120 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
121 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
122 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
123 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
124 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
125 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
126 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
127 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
128 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
129 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
130 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
131 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
132 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
133 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
134 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
135 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
136 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
137 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
138 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
139 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
140 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
141 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
142 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
143 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
144 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")));
145 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
146 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
147 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
148 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
149 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
150 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
151 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
152 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
153 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
154 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
155 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
156 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
157 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
158 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
159 default: return label;
164 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
165 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
167 #define SB_EXTRA_SORTING_FIELDS 5
168 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
169 void Scoreboard_InitScores()
173 ps_primary = ps_secondary = NULL;
174 ts_primary = ts_secondary = -1;
175 FOREACH(Scores, true, {
176 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
177 if(f == SFL_SORT_PRIO_PRIMARY)
179 if(f == SFL_SORT_PRIO_SECONDARY)
181 if(ps_primary == it || ps_secondary == it)
183 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
184 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
185 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
186 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
187 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
189 if(ps_secondary == NULL)
190 ps_secondary = ps_primary;
192 for(i = 0; i < MAX_TEAMSCORE; ++i)
194 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
195 if(f == SFL_SORT_PRIO_PRIMARY)
197 if(f == SFL_SORT_PRIO_SECONDARY)
200 if(ts_secondary == -1)
201 ts_secondary = ts_primary;
203 Cmd_Scoreboard_SetFields(0);
207 void Scoreboard_UpdatePlayerTeams()
211 for(pl = players.sort_next; pl; pl = pl.sort_next)
214 int Team = entcs_GetScoreTeam(pl.sv_entnum);
215 if(SetTeam(pl, Team))
218 Scoreboard_UpdatePlayerPos(pl);
222 pl = players.sort_next;
227 print(strcat("PNUM: ", ftos(num), "\n"));
232 int Scoreboard_CompareScore(int vl, int vr, int f)
234 TC(int, vl); TC(int, vr); TC(int, f);
235 if(f & SFL_ZERO_IS_WORST)
237 if(vl == 0 && vr != 0)
239 if(vl != 0 && vr == 0)
243 return IS_INCREASING(f);
245 return IS_DECREASING(f);
249 float Scoreboard_ComparePlayerScores(entity left, entity right)
251 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
252 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
259 if(vl == NUM_SPECTATOR)
261 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
263 if(!left.gotscores && right.gotscores)
270 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
274 if (!fld) fld = ps_primary;
275 else if (ps_secondary == ps_primary) continue;
276 else fld = ps_secondary;
280 fld = sb_extra_sorting_field[i];
281 if (fld == ps_primary || fld == ps_secondary) continue;
285 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
286 if (r >= 0) return r;
289 if (left.sv_entnum < right.sv_entnum)
295 void Scoreboard_UpdatePlayerPos(entity player)
298 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
300 SORT_SWAP(player, ent);
302 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
304 SORT_SWAP(ent, player);
308 float Scoreboard_CompareTeamScores(entity left, entity right)
310 if(left.team == NUM_SPECTATOR)
312 if(right.team == NUM_SPECTATOR)
317 for(int i = -2; i < MAX_TEAMSCORE; ++i)
321 if (fld_idx == -1) fld_idx = ts_primary;
322 else if (ts_secondary == ts_primary) continue;
323 else fld_idx = ts_secondary;
328 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
331 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
332 if (r >= 0) return r;
335 if (left.team < right.team)
341 void Scoreboard_UpdateTeamPos(entity Team)
344 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
346 SORT_SWAP(Team, ent);
348 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
350 SORT_SWAP(ent, Team);
354 void Cmd_Scoreboard_Help()
356 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
357 LOG_HELP(_("Usage:"));
358 LOG_HELP("^2scoreboard_columns_set ^3default");
359 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
360 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
361 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
362 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
363 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
364 LOG_HELP(_("The following field names are recognized (case insensitive):"));
370 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
371 "of game types, then a slash, to make the field show up only in these\n"
372 "or in all but these game types. You can also specify 'all' as a\n"
373 "field to show all fields available for the current game mode."));
376 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
377 "include/exclude ALL teams/noteams game modes."));
380 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
381 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
382 "right of the vertical bar aligned to the right."));
383 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
384 "other gamemodes except DM."));
387 // NOTE: adding a gametype with ? to not warn for an optional field
388 // make sure it's excluded in a previous exclusive rule, if any
389 // otherwise the previous exclusive rule warns anyway
390 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
391 #define SCOREBOARD_DEFAULT_COLUMNS \
392 "ping pl fps name |" \
393 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
394 " -teams,lms/deaths +ft,tdm/deaths" \
396 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
397 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
398 " +tdm,ft,dom,ons,as/teamkills"\
399 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
400 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
401 " +lms/lives +lms/rank" \
402 " +kh/kckills +kh/losses +kh/caps" \
403 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
404 " +as/objectives +nb/faults +nb/goals" \
405 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
406 " +dom/ticks +dom/takes" \
407 " -lms,rc,cts,inv,nb/score"
409 void Cmd_Scoreboard_SetFields(int argc)
414 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
418 return; // do nothing, we don't know gametype and scores yet
420 // sbt_fields uses strunzone on the titles!
421 if(!sbt_field_title[0])
422 for(i = 0; i < MAX_SBT_FIELDS; ++i)
423 sbt_field_title[i] = strzone("(null)");
425 // TODO: re enable with gametype dependant cvars?
426 if(argc < 3) // no arguments provided
427 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
430 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
434 if(argv(2) == "default" || argv(2) == "expand_default")
436 if(argv(2) == "expand_default")
437 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
438 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
440 else if(argv(2) == "all" || argv(2) == "ALL")
442 string s = "ping pl name |"; // scores without label (not really scores)
445 // scores without label
446 s = strcat(s, " ", "sum");
447 s = strcat(s, " ", "kdratio");
448 s = strcat(s, " ", "frags");
450 FOREACH(Scores, true, {
452 if(it != ps_secondary)
453 if(scores_label(it) != "")
454 s = strcat(s, " ", scores_label(it));
456 if(ps_secondary != ps_primary)
457 s = strcat(s, " ", scores_label(ps_secondary));
458 s = strcat(s, " ", scores_label(ps_primary));
459 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
466 hud_fontsize = HUD_GetFontsize("hud_fontsize");
468 for(i = 1; i < argc - 1; ++i)
471 bool nocomplain = false;
472 if(substring(str, 0, 1) == "?")
475 str = substring(str, 1, strlen(str) - 1);
478 slash = strstrofs(str, "/", 0);
481 pattern = substring(str, 0, slash);
482 str = substring(str, slash + 1, strlen(str) - (slash + 1));
484 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
488 str = strtolower(str);
489 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
490 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
495 // fields without a label (not networked via the score system)
496 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
497 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
498 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
499 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
500 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
501 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
502 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
503 default: // fields with a label
505 // map alternative labels
506 if (str == "damage") str = "dmg";
507 if (str == "damagetaken") str = "dmgtaken";
509 FOREACH(Scores, true, {
510 if (str == strtolower(scores_label(it))) {
512 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
516 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
517 if(!nocomplain && str != "fps") // server can disable the fps field
518 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
520 strfree(sbt_field_title[sbt_num_fields]);
521 sbt_field_size[sbt_num_fields] = 0;
525 sbt_field[sbt_num_fields] = j;
528 if(j == ps_secondary)
529 have_secondary = true;
534 if(sbt_num_fields >= MAX_SBT_FIELDS)
538 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
540 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
541 have_secondary = true;
542 if(ps_primary == ps_secondary)
543 have_secondary = true;
544 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
546 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
550 strfree(sbt_field_title[sbt_num_fields]);
551 for(i = sbt_num_fields; i > 0; --i)
553 sbt_field_title[i] = sbt_field_title[i-1];
554 sbt_field_size[i] = sbt_field_size[i-1];
555 sbt_field[i] = sbt_field[i-1];
557 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
558 sbt_field[0] = SP_NAME;
560 LOG_INFO("fixed missing field 'name'");
564 strfree(sbt_field_title[sbt_num_fields]);
565 for(i = sbt_num_fields; i > 1; --i)
567 sbt_field_title[i] = sbt_field_title[i-1];
568 sbt_field_size[i] = sbt_field_size[i-1];
569 sbt_field[i] = sbt_field[i-1];
571 sbt_field_title[1] = strzone("|");
572 sbt_field[1] = SP_SEPARATOR;
573 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
575 LOG_INFO("fixed missing field '|'");
578 else if(!have_separator)
580 strcpy(sbt_field_title[sbt_num_fields], "|");
581 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
582 sbt_field[sbt_num_fields] = SP_SEPARATOR;
584 LOG_INFO("fixed missing field '|'");
588 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
589 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
590 sbt_field[sbt_num_fields] = ps_secondary;
592 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
596 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
597 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
598 sbt_field[sbt_num_fields] = ps_primary;
600 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
604 sbt_field[sbt_num_fields] = SP_END;
607 string Scoreboard_AddPlayerId(string pl_name, entity pl)
609 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
610 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
611 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
615 vector sbt_field_rgb;
616 string sbt_field_icon0;
617 string sbt_field_icon1;
618 string sbt_field_icon2;
619 vector sbt_field_icon0_rgb;
620 vector sbt_field_icon1_rgb;
621 vector sbt_field_icon2_rgb;
622 string Scoreboard_GetName(entity pl)
624 if(ready_waiting && pl.ready)
626 sbt_field_icon0 = "gfx/scoreboard/player_ready";
630 int f = entcs_GetClientColors(pl.sv_entnum);
632 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
633 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
634 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
635 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
636 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
639 return entcs_GetName(pl.sv_entnum);
642 string Scoreboard_GetField(entity pl, PlayerScoreField field)
644 float tmp, num, denom;
647 sbt_field_rgb = '1 1 1';
648 sbt_field_icon0 = "";
649 sbt_field_icon1 = "";
650 sbt_field_icon2 = "";
651 sbt_field_icon0_rgb = '1 1 1';
652 sbt_field_icon1_rgb = '1 1 1';
653 sbt_field_icon2_rgb = '1 1 1';
658 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
659 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
663 tmp = max(0, min(220, f-80)) / 220;
664 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
670 f = pl.ping_packetloss;
671 tmp = pl.ping_movementloss;
672 if(f == 0 && tmp == 0)
674 str = ftos(ceil(f * 100));
676 str = strcat(str, "~", ftos(ceil(tmp * 100)));
677 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
678 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
682 str = Scoreboard_GetName(pl);
683 if (autocvar_hud_panel_scoreboard_playerid)
684 str = Scoreboard_AddPlayerId(str, pl);
688 f = pl.(scores(SP_KILLS));
689 f -= pl.(scores(SP_SUICIDES));
693 num = pl.(scores(SP_KILLS));
694 denom = pl.(scores(SP_DEATHS));
697 sbt_field_rgb = '0 1 0';
698 str = sprintf("%d", num);
699 } else if(num <= 0) {
700 sbt_field_rgb = '1 0 0';
701 str = sprintf("%.1f", num/denom);
703 str = sprintf("%.1f", num/denom);
707 f = pl.(scores(SP_KILLS));
708 f -= pl.(scores(SP_DEATHS));
711 sbt_field_rgb = '0 1 0';
713 sbt_field_rgb = '1 1 1';
715 sbt_field_rgb = '1 0 0';
721 float elo = pl.(scores(SP_ELO));
723 case -1: return "...";
724 case -2: return _("N/A");
725 default: return ftos(elo);
731 float fps = pl.(scores(SP_FPS));
734 sbt_field_rgb = '1 1 1';
735 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
737 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
738 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
742 case SP_DMG: case SP_DMGTAKEN:
743 return sprintf("%.1f k", pl.(scores(field)) / 1000);
745 default: case SP_SCORE:
746 tmp = pl.(scores(field));
747 f = scores_flags(field);
748 if(field == ps_primary)
749 sbt_field_rgb = '1 1 0';
750 else if(field == ps_secondary)
751 sbt_field_rgb = '0 1 1';
753 sbt_field_rgb = '1 1 1';
754 return ScoreString(f, tmp);
759 float sbt_fixcolumnwidth_len;
760 float sbt_fixcolumnwidth_iconlen;
761 float sbt_fixcolumnwidth_marginlen;
763 string Scoreboard_FixColumnWidth(int i, string str)
769 sbt_fixcolumnwidth_iconlen = 0;
771 if(sbt_field_icon0 != "")
773 sz = draw_getimagesize(sbt_field_icon0);
775 if(sbt_fixcolumnwidth_iconlen < f)
776 sbt_fixcolumnwidth_iconlen = f;
779 if(sbt_field_icon1 != "")
781 sz = draw_getimagesize(sbt_field_icon1);
783 if(sbt_fixcolumnwidth_iconlen < f)
784 sbt_fixcolumnwidth_iconlen = f;
787 if(sbt_field_icon2 != "")
789 sz = draw_getimagesize(sbt_field_icon2);
791 if(sbt_fixcolumnwidth_iconlen < f)
792 sbt_fixcolumnwidth_iconlen = f;
795 if(sbt_fixcolumnwidth_iconlen != 0)
797 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
798 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
801 sbt_fixcolumnwidth_marginlen = 0;
803 if(sbt_field[i] == SP_NAME) // name gets all remaining space
806 float remaining_space = 0;
807 for(j = 0; j < sbt_num_fields; ++j)
809 if (sbt_field[i] != SP_SEPARATOR)
810 remaining_space += sbt_field_size[j] + hud_fontsize.x;
811 sbt_field_size[i] = panel_size.x - remaining_space;
813 if (sbt_fixcolumnwidth_iconlen != 0)
814 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
815 float namesize = panel_size.x - remaining_space;
816 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
817 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
819 max_namesize = vid_conwidth - remaining_space;
822 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
824 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
825 if(sbt_field_size[i] < f)
826 sbt_field_size[i] = f;
831 void Scoreboard_initFieldSizes()
833 for(int i = 0; i < sbt_num_fields; ++i)
835 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
836 Scoreboard_FixColumnWidth(i, "");
840 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
843 vector column_dim = eY * panel_size.y;
845 column_dim.y -= 1.25 * hud_fontsize.y;
846 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
847 pos.x += hud_fontsize.x * 0.5;
848 for(i = 0; i < sbt_num_fields; ++i)
850 if(sbt_field[i] == SP_SEPARATOR)
852 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
855 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
856 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
857 pos.x += column_dim.x;
859 if(sbt_field[i] == SP_SEPARATOR)
861 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
862 for(i = sbt_num_fields - 1; i > 0; --i)
864 if(sbt_field[i] == SP_SEPARATOR)
867 pos.x -= sbt_field_size[i];
872 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
873 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
876 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
877 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
878 pos.x -= hud_fontsize.x;
883 pos.y += 1.25 * hud_fontsize.y;
887 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
889 TC(bool, is_self); TC(int, pl_number);
891 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
893 vector h_pos = item_pos;
894 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
895 // alternated rows highlighting
897 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
898 else if((sbt_highlight) && (!(pl_number % 2)))
899 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
901 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
903 vector pos = item_pos;
904 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
906 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
908 pos.x += hud_fontsize.x * 0.5;
909 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
910 vector tmp = '0 0 0';
912 PlayerScoreField field;
913 for(i = 0; i < sbt_num_fields; ++i)
915 field = sbt_field[i];
916 if(field == SP_SEPARATOR)
919 if(is_spec && field != SP_NAME && field != SP_PING) {
920 pos.x += sbt_field_size[i] + hud_fontsize.x;
923 str = Scoreboard_GetField(pl, field);
924 str = Scoreboard_FixColumnWidth(i, str);
926 pos.x += sbt_field_size[i] + hud_fontsize.x;
928 if(field == SP_NAME) {
929 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
930 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
932 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
933 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
936 tmp.x = sbt_field_size[i] + hud_fontsize.x;
937 if(sbt_field_icon0 != "")
938 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
939 if(sbt_field_icon1 != "")
940 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
941 if(sbt_field_icon2 != "")
942 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
945 if(sbt_field[i] == SP_SEPARATOR)
947 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
948 for(i = sbt_num_fields-1; i > 0; --i)
950 field = sbt_field[i];
951 if(field == SP_SEPARATOR)
954 if(is_spec && field != SP_NAME && field != SP_PING) {
955 pos.x -= sbt_field_size[i] + hud_fontsize.x;
959 str = Scoreboard_GetField(pl, field);
960 str = Scoreboard_FixColumnWidth(i, str);
962 if(field == SP_NAME) {
963 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
964 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
966 tmp.x = sbt_fixcolumnwidth_len;
967 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
970 tmp.x = sbt_field_size[i];
971 if(sbt_field_icon0 != "")
972 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
973 if(sbt_field_icon1 != "")
974 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
975 if(sbt_field_icon2 != "")
976 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
977 pos.x -= sbt_field_size[i] + hud_fontsize.x;
982 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
985 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
988 vector h_pos = item_pos;
989 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
991 bool complete = (this_team == NUM_SPECTATOR);
994 if((sbt_highlight) && (!(pl_number % 2)))
995 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
997 vector pos = item_pos;
998 pos.x += hud_fontsize.x * 0.5;
999 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1001 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1003 width_limit -= stringwidth("...", false, hud_fontsize);
1004 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1005 static float max_name_width = 0;
1007 float fieldsize = 0;
1008 float min_fieldsize = 0;
1009 float fieldpadding = hud_fontsize.x * 0.25;
1010 if(this_team == NUM_SPECTATOR)
1012 if(autocvar_hud_panel_scoreboard_spectators_showping)
1013 min_fieldsize = stringwidth("999", false, hud_fontsize);
1015 else if(autocvar_hud_panel_scoreboard_others_showscore)
1016 min_fieldsize = stringwidth("99", false, hud_fontsize);
1017 for(i = 0; pl; pl = pl.sort_next)
1019 if(pl.team != this_team)
1021 if(pl == ignored_pl)
1025 if(this_team == NUM_SPECTATOR)
1027 if(autocvar_hud_panel_scoreboard_spectators_showping)
1028 field = Scoreboard_GetField(pl, SP_PING);
1030 else if(autocvar_hud_panel_scoreboard_others_showscore)
1031 field = Scoreboard_GetField(pl, SP_SCORE);
1033 string str = entcs_GetName(pl.sv_entnum);
1034 if (autocvar_hud_panel_scoreboard_playerid)
1035 str = Scoreboard_AddPlayerId(str, pl);
1036 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1037 float column_width = stringwidth(str, true, hud_fontsize);
1038 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1040 if(column_width > max_name_width)
1041 max_name_width = column_width;
1042 column_width = max_name_width;
1046 fieldsize = stringwidth(field, false, hud_fontsize);
1047 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1050 if(pos.x + column_width > width_limit)
1055 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1060 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1061 pos.y += hud_fontsize.y * 1.25;
1065 vector name_pos = pos;
1066 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1067 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1068 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1071 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1072 h_size.y = hud_fontsize.y;
1073 vector field_pos = pos;
1074 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1075 field_pos.x += column_width - h_size.x;
1077 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1078 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1079 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1083 h_size.x = column_width + hud_fontsize.x * 0.25;
1084 h_size.y = hud_fontsize.y;
1085 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1087 pos.x += column_width;
1088 pos.x += hud_fontsize.x;
1090 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1093 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1095 int max_players = 999;
1096 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1098 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1101 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1102 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1103 height /= team_count;
1106 height -= panel_bg_padding * 2; // - padding
1107 max_players = floor(height / (hud_fontsize.y * 1.25));
1108 if(max_players <= 1)
1110 if(max_players == tm.team_size)
1115 entity me = playerslots[current_player];
1117 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1118 panel_size.y += panel_bg_padding * 2;
1121 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1122 if(panel.current_panel_bg != "0")
1123 end_pos.y += panel_bg_border * 2;
1125 if(panel_bg_padding)
1127 panel_pos += '1 1 0' * panel_bg_padding;
1128 panel_size -= '2 2 0' * panel_bg_padding;
1132 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1136 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1138 pos.y += 1.25 * hud_fontsize.y;
1141 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1143 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1146 // print header row and highlight columns
1147 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1149 // fill the table and draw the rows
1150 bool is_self = false;
1151 bool self_shown = false;
1153 for(pl = players.sort_next; pl; pl = pl.sort_next)
1155 if(pl.team != tm.team)
1157 if(i == max_players - 2 && pl != me)
1159 if(!self_shown && me.team == tm.team)
1161 Scoreboard_DrawItem(pos, rgb, me, true, i);
1163 pos.y += 1.25 * hud_fontsize.y;
1167 if(i >= max_players - 1)
1169 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1172 is_self = (pl.sv_entnum == current_player);
1173 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1176 pos.y += 1.25 * hud_fontsize.y;
1180 panel_size.x += panel_bg_padding * 2; // restore initial width
1184 bool Scoreboard_WouldDraw()
1186 if (MUTATOR_CALLHOOK(DrawScoreboard))
1188 else if (QuickMenu_IsOpened())
1190 else if (HUD_Radar_Clickable())
1192 else if (scoreboard_showscores)
1194 else if (intermission == 1)
1196 else if (intermission == 2)
1198 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1199 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1203 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1208 float average_accuracy;
1209 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1211 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1213 WepSet weapons_stat = WepSet_GetFromStat();
1214 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1215 int disownedcnt = 0;
1217 FOREACH(Weapons, it != WEP_Null, {
1218 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1220 WepSet set = it.m_wepset;
1221 if(it.spawnflags & WEP_TYPE_OTHER)
1226 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1228 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1235 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1236 if (weapon_cnt <= 0) return pos;
1239 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1241 int columnns = ceil(weapon_cnt / rows);
1243 float weapon_height = 29;
1244 float height = hud_fontsize.y + weapon_height;
1246 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);
1247 pos.y += 1.25 * hud_fontsize.y;
1248 if(panel.current_panel_bg != "0")
1249 pos.y += panel_bg_border;
1252 panel_size.y = height * rows;
1253 panel_size.y += panel_bg_padding * 2;
1255 float panel_bg_alpha_save = panel_bg_alpha;
1256 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1258 panel_bg_alpha = panel_bg_alpha_save;
1260 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1261 if(panel.current_panel_bg != "0")
1262 end_pos.y += panel_bg_border * 2;
1264 if(panel_bg_padding)
1266 panel_pos += '1 1 0' * panel_bg_padding;
1267 panel_size -= '2 2 0' * panel_bg_padding;
1271 vector tmp = panel_size;
1273 float weapon_width = tmp.x / columnns / rows;
1276 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1280 // column highlighting
1281 for (int i = 0; i < columnns; ++i)
1283 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);
1286 for (int i = 0; i < rows; ++i)
1287 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1290 average_accuracy = 0;
1291 int weapons_with_stats = 0;
1293 pos.x += weapon_width / 2;
1295 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1298 Accuracy_LoadColors();
1300 float oldposx = pos.x;
1304 FOREACH(Weapons, it != WEP_Null, {
1305 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1307 WepSet set = it.m_wepset;
1308 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1310 if (it.spawnflags & WEP_TYPE_OTHER)
1314 if (weapon_stats >= 0)
1315 weapon_alpha = sbt_fg_alpha;
1317 weapon_alpha = 0.2 * sbt_fg_alpha;
1320 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1322 if (weapon_stats >= 0) {
1323 weapons_with_stats += 1;
1324 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1327 s = sprintf("%d%%", weapon_stats * 100);
1330 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1332 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1333 rgb = Accuracy_GetColor(weapon_stats);
1335 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1337 tmpos.x += weapon_width * rows;
1338 pos.x += weapon_width * rows;
1339 if (rows == 2 && column == columnns - 1) {
1347 if (weapons_with_stats)
1348 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1350 panel_size.x += panel_bg_padding * 2; // restore initial width
1355 bool is_item_filtered(entity it)
1357 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1359 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1362 if (it.instanceOfArmor || it.instanceOfHealth)
1364 int ha_mask = floor(mask) % 10;
1367 default: return false;
1368 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1369 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1370 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1371 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1374 if (it.instanceOfAmmo)
1376 int ammo_mask = floor(mask / 10) % 10;
1377 return (ammo_mask == 1);
1382 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1384 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1386 int disowned_cnt = 0;
1387 int uninteresting_cnt = 0;
1388 IL_EACH(default_order_items, true, {
1389 int q = g_inventory.inv_items[it.m_id];
1390 //q = 1; // debug: display all items
1391 if (is_item_filtered(it))
1392 ++uninteresting_cnt;
1396 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1397 int n = items_cnt - disowned_cnt;
1398 if (n <= 0) return pos;
1400 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1401 int columnns = max(6, ceil(n / rows));
1404 float fontsize = height * 1/3;
1405 float item_height = height * 2/3;
1407 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1408 pos.y += 1.25 * hud_fontsize.y;
1409 if(panel.current_panel_bg != "0")
1410 pos.y += panel_bg_border;
1413 panel_size.y = height * rows;
1414 panel_size.y += panel_bg_padding * 2;
1416 float panel_bg_alpha_save = panel_bg_alpha;
1417 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1419 panel_bg_alpha = panel_bg_alpha_save;
1421 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1422 if(panel.current_panel_bg != "0")
1423 end_pos.y += panel_bg_border * 2;
1425 if(panel_bg_padding)
1427 panel_pos += '1 1 0' * panel_bg_padding;
1428 panel_size -= '2 2 0' * panel_bg_padding;
1432 vector tmp = panel_size;
1434 float item_width = tmp.x / columnns / rows;
1437 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1441 // column highlighting
1442 for (int i = 0; i < columnns; ++i)
1444 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);
1447 for (int i = 0; i < rows; ++i)
1448 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);
1452 pos.x += item_width / 2;
1454 float oldposx = pos.x;
1458 IL_EACH(default_order_items, !is_item_filtered(it), {
1459 int n = g_inventory.inv_items[it.m_id];
1460 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1461 if (n <= 0) continue;
1462 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);
1464 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1465 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);
1466 tmpos.x += item_width * rows;
1467 pos.x += item_width * rows;
1468 if (rows == 2 && column == columnns - 1) {
1476 panel_size.x += panel_bg_padding * 2; // restore initial width
1481 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1483 pos.x += hud_fontsize.x * 0.25;
1484 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1485 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1486 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1488 pos.y += hud_fontsize.y;
1493 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1494 float stat_secrets_found, stat_secrets_total;
1495 float stat_monsters_killed, stat_monsters_total;
1499 // get monster stats
1500 stat_monsters_killed = STAT(MONSTERS_KILLED);
1501 stat_monsters_total = STAT(MONSTERS_TOTAL);
1503 // get secrets stats
1504 stat_secrets_found = STAT(SECRETS_FOUND);
1505 stat_secrets_total = STAT(SECRETS_TOTAL);
1507 // get number of rows
1508 if(stat_secrets_total)
1510 if(stat_monsters_total)
1513 // if no rows, return
1517 // draw table header
1518 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1519 pos.y += 1.25 * hud_fontsize.y;
1520 if(panel.current_panel_bg != "0")
1521 pos.y += panel_bg_border;
1524 panel_size.y = hud_fontsize.y * rows;
1525 panel_size.y += panel_bg_padding * 2;
1528 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1529 if(panel.current_panel_bg != "0")
1530 end_pos.y += panel_bg_border * 2;
1532 if(panel_bg_padding)
1534 panel_pos += '1 1 0' * panel_bg_padding;
1535 panel_size -= '2 2 0' * panel_bg_padding;
1539 vector tmp = panel_size;
1542 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1545 if(stat_monsters_total)
1547 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1548 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1552 if(stat_secrets_total)
1554 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1555 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1558 panel_size.x += panel_bg_padding * 2; // restore initial width
1563 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1566 RANKINGS_RECEIVED_CNT = 0;
1567 for (i=RANKINGS_CNT-1; i>=0; --i)
1569 ++RANKINGS_RECEIVED_CNT;
1571 if (RANKINGS_RECEIVED_CNT == 0)
1574 vector hl_rgb = rgb + '0.5 0.5 0.5';
1576 pos.y += hud_fontsize.y;
1577 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1578 pos.y += 1.25 * hud_fontsize.y;
1579 if(panel.current_panel_bg != "0")
1580 pos.y += panel_bg_border;
1585 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1587 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1592 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1594 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1598 float ranksize = 3 * hud_fontsize.x;
1599 float timesize = 5 * hud_fontsize.x;
1600 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1601 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1602 columns = min(columns, RANKINGS_RECEIVED_CNT);
1604 // expand name column to fill the entire row
1605 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1606 namesize += available_space;
1607 columnsize.x += available_space;
1609 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1610 panel_size.y += panel_bg_padding * 2;
1614 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1615 if(panel.current_panel_bg != "0")
1616 end_pos.y += panel_bg_border * 2;
1618 if(panel_bg_padding)
1620 panel_pos += '1 1 0' * panel_bg_padding;
1621 panel_size -= '2 2 0' * panel_bg_padding;
1627 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1629 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1631 int column = 0, j = 0;
1632 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1633 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1640 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1641 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1642 else if(!((j + column) & 1) && sbt_highlight)
1643 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1645 str = count_ordinal(i+1);
1646 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1647 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1648 str = ColorTranslateRGB(grecordholder[i]);
1650 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1651 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1653 pos.y += 1.25 * hud_fontsize.y;
1655 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1659 pos.x += panel_size.x / columns;
1660 pos.y = panel_pos.y;
1663 strfree(zoned_name_self);
1665 panel_size.x += panel_bg_padding * 2; // restore initial width
1669 float scoreboard_time;
1670 bool have_weapon_stats;
1671 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1673 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1675 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1678 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1679 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1685 if (!have_weapon_stats)
1687 FOREACH(Weapons, it != WEP_Null, {
1688 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1689 if (weapon_stats >= 0)
1691 have_weapon_stats = true;
1695 if (!have_weapon_stats)
1702 bool have_item_stats;
1703 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1705 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1707 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1710 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1711 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1717 if (!have_item_stats)
1719 IL_EACH(default_order_items, true, {
1720 if (!is_item_filtered(it))
1722 int q = g_inventory.inv_items[it.m_id];
1723 //q = 1; // debug: display all items
1726 have_item_stats = true;
1731 if (!have_item_stats)
1738 void Scoreboard_Draw()
1740 if(!autocvar__hud_configure)
1742 if(!hud_draw_maximized) return;
1744 // frametime checks allow to toggle the scoreboard even when the game is paused
1745 if(scoreboard_active) {
1746 if (scoreboard_fade_alpha == 0)
1747 scoreboard_time = time;
1748 if(hud_configure_menu_open == 1)
1749 scoreboard_fade_alpha = 1;
1750 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1751 if (scoreboard_fadeinspeed && frametime)
1752 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1754 scoreboard_fade_alpha = 1;
1755 if(hud_fontsize_str != autocvar_hud_fontsize)
1757 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1758 Scoreboard_initFieldSizes();
1759 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1763 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1764 if (scoreboard_fadeoutspeed && frametime)
1765 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1767 scoreboard_fade_alpha = 0;
1770 if (!scoreboard_fade_alpha)
1772 scoreboard_acc_fade_alpha = 0;
1773 scoreboard_itemstats_fade_alpha = 0;
1778 scoreboard_fade_alpha = 0;
1780 if (autocvar_hud_panel_scoreboard_dynamichud)
1783 HUD_Scale_Disable();
1785 if(scoreboard_fade_alpha <= 0)
1787 panel_fade_alpha *= scoreboard_fade_alpha;
1788 HUD_Panel_LoadCvars();
1790 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1791 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1792 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1793 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1794 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1795 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1796 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1798 // don't overlap with con_notify
1799 if(!autocvar__hud_configure)
1800 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1802 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1803 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1804 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1805 panel_size.x = fixed_scoreboard_width;
1807 Scoreboard_UpdatePlayerTeams();
1809 float initial_pos_y = panel_pos.y;
1810 vector pos = panel_pos;
1815 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1817 // Begin of Game Info Section
1818 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1819 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1821 // Game Info: Game Type
1822 str = MapInfo_Type_ToText(gametype);
1823 draw_beginBoldFont();
1824 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);
1827 // Game Info: Game Detail
1828 float tl = STAT(TIMELIMIT);
1829 float fl = STAT(FRAGLIMIT);
1830 float ll = STAT(LEADLIMIT);
1831 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1834 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1835 if(!gametype.m_hidelimits)
1840 str = strcat(str, "^7 / "); // delimiter
1843 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1844 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1845 (teamscores_label(ts_primary) == "fastest") ? "" :
1846 TranslateScoresLabel(teamscores_label(ts_primary))));
1850 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1851 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1852 (scores_label(ps_primary) == "fastest") ? "" :
1853 TranslateScoresLabel(scores_label(ps_primary))));
1858 if(tl > 0 || fl > 0)
1861 if (ll_and_fl && fl > 0)
1862 str = strcat(str, "^7 & ");
1864 str = strcat(str, "^7 / ");
1869 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1870 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1871 (teamscores_label(ts_primary) == "fastest") ? "" :
1872 TranslateScoresLabel(teamscores_label(ts_primary))));
1876 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1877 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1878 (scores_label(ps_primary) == "fastest") ? "" :
1879 TranslateScoresLabel(scores_label(ps_primary))));
1884 pos.y += sb_gameinfo_type_fontsize.y;
1885 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
1887 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1888 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1889 // End of Game Info Section
1891 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1892 if(panel.current_panel_bg != "0")
1893 pos.y += panel_bg_border;
1895 // Draw the scoreboard
1896 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1899 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1903 vector panel_bg_color_save = panel_bg_color;
1904 vector team_score_baseoffset;
1905 vector team_size_baseoffset;
1906 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1908 // put team score to the left of scoreboard (and team size to the right)
1909 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1910 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1911 if(panel.current_panel_bg != "0")
1913 team_score_baseoffset.x -= panel_bg_border;
1914 team_size_baseoffset.x += panel_bg_border;
1919 // put team score to the right of scoreboard (and team size to the left)
1920 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1921 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1922 if(panel.current_panel_bg != "0")
1924 team_score_baseoffset.x += panel_bg_border;
1925 team_size_baseoffset.x -= panel_bg_border;
1929 int team_size_total = 0;
1930 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1932 // calculate team size total (sum of all team sizes)
1933 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1934 if(tm.team != NUM_SPECTATOR)
1935 team_size_total += tm.team_size;
1938 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1940 if(tm.team == NUM_SPECTATOR)
1945 draw_beginBoldFont();
1946 vector rgb = Team_ColorRGB(tm.team);
1947 str = ftos(tm.(teamscores(ts_primary)));
1948 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1950 // team score on the left (default)
1951 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1955 // team score on the right
1956 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1958 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1960 // team size (if set to show on the side)
1961 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1963 // calculate the starting position for the whole team size info string
1964 str = sprintf("%d/%d", tm.team_size, team_size_total);
1965 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1967 // team size on the left
1968 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1972 // team size on the right
1973 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1975 str = sprintf("%d", tm.team_size);
1976 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1977 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1978 str = sprintf("/%d", team_size_total);
1979 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1983 // secondary score, e.g. keyhunt
1984 if(ts_primary != ts_secondary)
1986 str = ftos(tm.(teamscores(ts_secondary)));
1987 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1990 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1995 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1998 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2001 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2002 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2003 else if(panel_bg_color_team > 0)
2004 panel_bg_color = rgb * panel_bg_color_team;
2006 panel_bg_color = rgb;
2007 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2009 panel_bg_color = panel_bg_color_save;
2013 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2014 if(tm.team != NUM_SPECTATOR)
2017 // display it anyway
2018 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2021 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2022 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2023 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2024 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2026 if(MUTATOR_CALLHOOK(ShowRankings)) {
2027 string ranktitle = M_ARGV(0, string);
2028 if(race_speedaward) {
2029 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);
2030 pos.y += 1.25 * hud_fontsize.y;
2032 if(race_speedaward_alltimebest) {
2033 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);
2034 pos.y += 1.25 * hud_fontsize.y;
2036 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2039 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2042 for(pl = players.sort_next; pl; pl = pl.sort_next)
2044 if(pl.team == NUM_SPECTATOR)
2046 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2047 if(tm.team == NUM_SPECTATOR)
2049 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2050 draw_beginBoldFont();
2051 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2053 pos.y += 1.25 * hud_fontsize.y;
2055 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2056 pos.y += 1.25 * hud_fontsize.y;
2063 // print information about respawn status
2064 float respawn_time = STAT(RESPAWN_TIME);
2068 if(respawn_time < 0)
2070 // a negative number means we are awaiting respawn, time value is still the same
2071 respawn_time *= -1; // remove mark now that we checked it
2073 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2074 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2076 str = sprintf(_("^1Respawning in ^3%s^1..."),
2077 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2078 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2080 count_seconds(ceil(respawn_time - time))
2084 else if(time < respawn_time)
2086 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2087 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2088 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2090 count_seconds(ceil(respawn_time - time))
2094 else if(time >= respawn_time)
2095 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2097 pos.y += 1.2 * hud_fontsize.y;
2098 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2101 pos.y += 2 * hud_fontsize.y;
2102 if (scoreboard_fade_alpha < 1)
2103 scoreboard_bottom = initial_pos_y + (pos.y - initial_pos_y) * scoreboard_fade_alpha;
2104 else if (pos.y != scoreboard_bottom)
2106 if (pos.y > scoreboard_bottom)
2107 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - initial_pos_y));
2109 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - initial_pos_y));