1 #include "scoreboard.qh"
3 #include <client/draw.qh>
4 #include <client/hud/panel/chat.qh>
5 #include <client/hud/panel/quickmenu.qh>
6 #include <client/hud/panel/racetimer.qh>
7 #include <client/hud/panel/weapons.qh>
8 #include <common/constants.qh>
9 #include <common/ent_cs.qh>
10 #include <common/mapinfo.qh>
11 #include <common/minigames/cl_minigames.qh>
12 #include <common/net_linked.qh>
13 #include <common/scores.qh>
14 #include <common/stats.qh>
15 #include <common/teams.qh>
16 #include <common/items/inventory.qh>
20 void Scoreboard_Draw_Export(int fh)
22 // allow saving cvars that aesthetically change the panel into hud skin files
23 HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
24 HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
25 HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
26 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
27 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
28 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
29 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
30 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
31 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
32 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
33 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_eliminated");
34 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
35 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
36 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
39 const int MAX_SBT_FIELDS = MAX_SCORE;
41 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
42 float sbt_field_size[MAX_SBT_FIELDS + 1];
43 string sbt_field_title[MAX_SBT_FIELDS + 1];
46 string autocvar_hud_fontsize;
47 string hud_fontsize_str;
52 float sbt_fg_alpha_self;
54 float sbt_highlight_alpha;
55 float sbt_highlight_alpha_self;
56 float sbt_highlight_alpha_eliminated;
58 // provide basic panel cvars to old clients
59 // TODO remove them after a future release (0.8.2+)
60 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
61 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
62 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
63 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
64 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
65 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
66 noref string autocvar_hud_panel_scoreboard_bg_border = "";
67 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
69 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
70 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
71 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
72 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
73 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
74 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
75 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
76 bool autocvar_hud_panel_scoreboard_table_highlight = true;
77 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
78 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
79 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
80 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
81 float autocvar_hud_panel_scoreboard_namesize = 15;
82 float autocvar_hud_panel_scoreboard_team_size_position = 0;
84 bool autocvar_hud_panel_scoreboard_accuracy = true;
85 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
86 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
87 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
88 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
90 bool autocvar_hud_panel_scoreboard_itemstats = true;
91 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
92 bool autocvar_hud_panel_scoreboard_itemstats_filter = true;
93 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
94 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
96 bool autocvar_hud_panel_scoreboard_dynamichud = false;
98 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
99 bool autocvar_hud_panel_scoreboard_others_showscore = true;
100 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
101 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
102 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
103 bool autocvar_hud_panel_scoreboard_playerid = false;
104 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
105 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
107 // mode 0: returns translated label
108 // mode 1: prints name and description of all the labels
109 string Label_getInfo(string label, int mode)
112 label = "bckills"; // first case in the switch
116 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
117 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
118 case "caps": if (!mode) return CTX(_("SCO^caps")); else LOG_HELP(strcat("^3", "caps", " ^7", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
119 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
120 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
121 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
122 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
123 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
124 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
125 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
126 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
127 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
128 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
129 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
130 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
131 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
132 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
133 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
134 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
135 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
136 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
137 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
138 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
139 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
140 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
141 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
142 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
143 case "pickups": if (!mode) return CTX(_("SCO^pickups")); else LOG_HELP(strcat("^3", "pickups", " ^7", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
144 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
145 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
146 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
147 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
148 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
149 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
150 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
151 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
152 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
153 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
154 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
155 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
156 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
157 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
158 default: return label;
163 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
164 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
166 #define SB_EXTRA_SORTING_FIELDS 5
167 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
168 void Scoreboard_InitScores()
172 ps_primary = ps_secondary = NULL;
173 ts_primary = ts_secondary = -1;
174 FOREACH(Scores, true, {
175 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
176 if(f == SFL_SORT_PRIO_PRIMARY)
178 if(f == SFL_SORT_PRIO_SECONDARY)
180 if(ps_primary == it || ps_secondary == it)
182 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
183 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
184 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
185 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
186 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
188 if(ps_secondary == NULL)
189 ps_secondary = ps_primary;
191 for(i = 0; i < MAX_TEAMSCORE; ++i)
193 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
194 if(f == SFL_SORT_PRIO_PRIMARY)
196 if(f == SFL_SORT_PRIO_SECONDARY)
199 if(ts_secondary == -1)
200 ts_secondary = ts_primary;
202 Cmd_Scoreboard_SetFields(0);
206 void Scoreboard_UpdatePlayerTeams()
210 for(pl = players.sort_next; pl; pl = pl.sort_next)
213 int Team = entcs_GetScoreTeam(pl.sv_entnum);
214 if(SetTeam(pl, Team))
217 Scoreboard_UpdatePlayerPos(pl);
221 pl = players.sort_next;
226 print(strcat("PNUM: ", ftos(num), "\n"));
231 int Scoreboard_CompareScore(int vl, int vr, int f)
233 TC(int, vl); TC(int, vr); TC(int, f);
234 if(f & SFL_ZERO_IS_WORST)
236 if(vl == 0 && vr != 0)
238 if(vl != 0 && vr == 0)
242 return IS_INCREASING(f);
244 return IS_DECREASING(f);
248 float Scoreboard_ComparePlayerScores(entity left, entity right)
251 vl = entcs_GetTeam(left.sv_entnum);
252 vr = entcs_GetTeam(right.sv_entnum);
264 if(vl == NUM_SPECTATOR)
266 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
268 if(!left.gotscores && right.gotscores)
274 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
278 if (!fld) fld = ps_primary;
279 else if (ps_secondary == ps_primary) continue;
280 else fld = ps_secondary;
284 fld = sb_extra_sorting_field[i];
285 if (fld == ps_primary || fld == ps_secondary) continue;
289 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
290 if (r >= 0) return r;
293 if (left.sv_entnum < right.sv_entnum)
299 void Scoreboard_UpdatePlayerPos(entity player)
302 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
304 SORT_SWAP(player, ent);
306 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
308 SORT_SWAP(ent, player);
312 float Scoreboard_CompareTeamScores(entity left, entity right)
316 if(left.team == NUM_SPECTATOR)
318 if(right.team == NUM_SPECTATOR)
321 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
325 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
329 for(i = 0; i < MAX_TEAMSCORE; ++i)
331 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
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")
443 string s = "ping pl name |"; // scores without a label
444 FOREACH(Scores, true, {
446 if(it != ps_secondary)
447 if(scores_label(it) != "")
448 s = strcat(s, " ", scores_label(it));
450 if(ps_secondary != ps_primary)
451 s = strcat(s, " ", scores_label(ps_secondary));
452 s = strcat(s, " ", scores_label(ps_primary));
453 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
460 hud_fontsize = HUD_GetFontsize("hud_fontsize");
462 for(i = 1; i < argc - 1; ++i)
465 bool nocomplain = false;
466 if(substring(str, 0, 1) == "?")
469 str = substring(str, 1, strlen(str) - 1);
472 slash = strstrofs(str, "/", 0);
475 pattern = substring(str, 0, slash);
476 str = substring(str, slash + 1, strlen(str) - (slash + 1));
478 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
482 str = strtolower(str);
483 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
484 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
489 // fields without a label (not networked via the score system)
490 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
491 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
492 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
493 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
494 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
495 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
496 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
497 default: // fields with a label
499 // map alternative labels
500 if (str == "damage") str = "dmg";
501 if (str == "damagetaken") str = "dmgtaken";
503 FOREACH(Scores, true, {
504 if (str == strtolower(scores_label(it))) {
506 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
510 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
511 if(!nocomplain && str != "fps") // server can disable the fps field
512 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
514 strfree(sbt_field_title[sbt_num_fields]);
515 sbt_field_size[sbt_num_fields] = 0;
519 sbt_field[sbt_num_fields] = j;
522 if(j == ps_secondary)
523 have_secondary = true;
528 if(sbt_num_fields >= MAX_SBT_FIELDS)
532 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
534 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
535 have_secondary = true;
536 if(ps_primary == ps_secondary)
537 have_secondary = true;
538 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
540 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
544 strfree(sbt_field_title[sbt_num_fields]);
545 for(i = sbt_num_fields; i > 0; --i)
547 sbt_field_title[i] = sbt_field_title[i-1];
548 sbt_field_size[i] = sbt_field_size[i-1];
549 sbt_field[i] = sbt_field[i-1];
551 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
552 sbt_field[0] = SP_NAME;
554 LOG_INFO("fixed missing field 'name'");
558 strfree(sbt_field_title[sbt_num_fields]);
559 for(i = sbt_num_fields; i > 1; --i)
561 sbt_field_title[i] = sbt_field_title[i-1];
562 sbt_field_size[i] = sbt_field_size[i-1];
563 sbt_field[i] = sbt_field[i-1];
565 sbt_field_title[1] = strzone("|");
566 sbt_field[1] = SP_SEPARATOR;
567 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
569 LOG_INFO("fixed missing field '|'");
572 else if(!have_separator)
574 strcpy(sbt_field_title[sbt_num_fields], "|");
575 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
576 sbt_field[sbt_num_fields] = SP_SEPARATOR;
578 LOG_INFO("fixed missing field '|'");
582 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
583 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
584 sbt_field[sbt_num_fields] = ps_secondary;
586 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
590 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
591 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
592 sbt_field[sbt_num_fields] = ps_primary;
594 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
598 sbt_field[sbt_num_fields] = SP_END;
601 string Scoreboard_AddPlayerId(string pl_name, entity pl)
603 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
604 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
605 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
609 vector sbt_field_rgb;
610 string sbt_field_icon0;
611 string sbt_field_icon1;
612 string sbt_field_icon2;
613 vector sbt_field_icon0_rgb;
614 vector sbt_field_icon1_rgb;
615 vector sbt_field_icon2_rgb;
616 string Scoreboard_GetName(entity pl)
618 if(ready_waiting && pl.ready)
620 sbt_field_icon0 = "gfx/scoreboard/player_ready";
624 int f = entcs_GetClientColors(pl.sv_entnum);
626 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
627 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
628 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
629 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
630 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
633 return entcs_GetName(pl.sv_entnum);
636 string Scoreboard_GetField(entity pl, PlayerScoreField field)
638 float tmp, num, denom;
641 sbt_field_rgb = '1 1 1';
642 sbt_field_icon0 = "";
643 sbt_field_icon1 = "";
644 sbt_field_icon2 = "";
645 sbt_field_icon0_rgb = '1 1 1';
646 sbt_field_icon1_rgb = '1 1 1';
647 sbt_field_icon2_rgb = '1 1 1';
652 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
653 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
657 tmp = max(0, min(220, f-80)) / 220;
658 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
664 f = pl.ping_packetloss;
665 tmp = pl.ping_movementloss;
666 if(f == 0 && tmp == 0)
668 str = ftos(ceil(f * 100));
670 str = strcat(str, "~", ftos(ceil(tmp * 100)));
671 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
672 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
676 str = Scoreboard_GetName(pl);
677 if (autocvar_hud_panel_scoreboard_playerid)
678 str = Scoreboard_AddPlayerId(str, pl);
682 f = pl.(scores(SP_KILLS));
683 f -= pl.(scores(SP_SUICIDES));
687 num = pl.(scores(SP_KILLS));
688 denom = pl.(scores(SP_DEATHS));
691 sbt_field_rgb = '0 1 0';
692 str = sprintf("%d", num);
693 } else if(num <= 0) {
694 sbt_field_rgb = '1 0 0';
695 str = sprintf("%.1f", num/denom);
697 str = sprintf("%.1f", num/denom);
701 f = pl.(scores(SP_KILLS));
702 f -= pl.(scores(SP_DEATHS));
705 sbt_field_rgb = '0 1 0';
707 sbt_field_rgb = '1 1 1';
709 sbt_field_rgb = '1 0 0';
715 float elo = pl.(scores(SP_ELO));
717 case -1: return "...";
718 case -2: return _("N/A");
719 default: return ftos(elo);
725 float fps = pl.(scores(SP_FPS));
728 sbt_field_rgb = '1 1 1';
729 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
731 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
732 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
736 case SP_DMG: case SP_DMGTAKEN:
737 return sprintf("%.1f k", pl.(scores(field)) / 1000);
739 default: case SP_SCORE:
740 tmp = pl.(scores(field));
741 f = scores_flags(field);
742 if(field == ps_primary)
743 sbt_field_rgb = '1 1 0';
744 else if(field == ps_secondary)
745 sbt_field_rgb = '0 1 1';
747 sbt_field_rgb = '1 1 1';
748 return ScoreString(f, tmp);
753 float sbt_fixcolumnwidth_len;
754 float sbt_fixcolumnwidth_iconlen;
755 float sbt_fixcolumnwidth_marginlen;
757 string Scoreboard_FixColumnWidth(int i, string str)
763 sbt_fixcolumnwidth_iconlen = 0;
765 if(sbt_field_icon0 != "")
767 sz = draw_getimagesize(sbt_field_icon0);
769 if(sbt_fixcolumnwidth_iconlen < f)
770 sbt_fixcolumnwidth_iconlen = f;
773 if(sbt_field_icon1 != "")
775 sz = draw_getimagesize(sbt_field_icon1);
777 if(sbt_fixcolumnwidth_iconlen < f)
778 sbt_fixcolumnwidth_iconlen = f;
781 if(sbt_field_icon2 != "")
783 sz = draw_getimagesize(sbt_field_icon2);
785 if(sbt_fixcolumnwidth_iconlen < f)
786 sbt_fixcolumnwidth_iconlen = f;
789 if(sbt_fixcolumnwidth_iconlen != 0)
791 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
792 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
795 sbt_fixcolumnwidth_marginlen = 0;
797 if(sbt_field[i] == SP_NAME) // name gets all remaining space
800 float remaining_space = 0;
801 for(j = 0; j < sbt_num_fields; ++j)
803 if (sbt_field[i] != SP_SEPARATOR)
804 remaining_space += sbt_field_size[j] + hud_fontsize.x;
805 sbt_field_size[i] = panel_size.x - remaining_space;
807 if (sbt_fixcolumnwidth_iconlen != 0)
808 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
809 float namesize = panel_size.x - remaining_space;
810 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
811 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
813 max_namesize = vid_conwidth - remaining_space;
816 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
818 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
819 if(sbt_field_size[i] < f)
820 sbt_field_size[i] = f;
825 void Scoreboard_initFieldSizes()
827 for(int i = 0; i < sbt_num_fields; ++i)
829 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
830 Scoreboard_FixColumnWidth(i, "");
834 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
837 vector column_dim = eY * panel_size.y;
839 column_dim.y -= 1.25 * hud_fontsize.y;
840 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
841 pos.x += hud_fontsize.x * 0.5;
842 for(i = 0; i < sbt_num_fields; ++i)
844 if(sbt_field[i] == SP_SEPARATOR)
846 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
849 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
850 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
851 pos.x += column_dim.x;
853 if(sbt_field[i] == SP_SEPARATOR)
855 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
856 for(i = sbt_num_fields - 1; i > 0; --i)
858 if(sbt_field[i] == SP_SEPARATOR)
861 pos.x -= sbt_field_size[i];
866 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
867 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
870 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
871 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
872 pos.x -= hud_fontsize.x;
877 pos.y += 1.25 * hud_fontsize.y;
881 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
883 TC(bool, is_self); TC(int, pl_number);
885 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
887 vector h_pos = item_pos;
888 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
889 // alternated rows highlighting
891 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
892 else if((sbt_highlight) && (!(pl_number % 2)))
893 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
895 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
897 vector pos = item_pos;
898 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
900 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
902 pos.x += hud_fontsize.x * 0.5;
903 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
904 vector tmp = '0 0 0';
906 PlayerScoreField field;
907 for(i = 0; i < sbt_num_fields; ++i)
909 field = sbt_field[i];
910 if(field == SP_SEPARATOR)
913 if(is_spec && field != SP_NAME && field != SP_PING) {
914 pos.x += sbt_field_size[i] + hud_fontsize.x;
917 str = Scoreboard_GetField(pl, field);
918 str = Scoreboard_FixColumnWidth(i, str);
920 pos.x += sbt_field_size[i] + hud_fontsize.x;
922 if(field == SP_NAME) {
923 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
924 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
926 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
927 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
930 tmp.x = sbt_field_size[i] + hud_fontsize.x;
931 if(sbt_field_icon0 != "")
932 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
933 if(sbt_field_icon1 != "")
934 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
935 if(sbt_field_icon2 != "")
936 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
939 if(sbt_field[i] == SP_SEPARATOR)
941 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
942 for(i = sbt_num_fields-1; i > 0; --i)
944 field = sbt_field[i];
945 if(field == SP_SEPARATOR)
948 if(is_spec && field != SP_NAME && field != SP_PING) {
949 pos.x -= sbt_field_size[i] + hud_fontsize.x;
953 str = Scoreboard_GetField(pl, field);
954 str = Scoreboard_FixColumnWidth(i, str);
956 if(field == SP_NAME) {
957 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
958 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
960 tmp.x = sbt_fixcolumnwidth_len;
961 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
964 tmp.x = sbt_field_size[i];
965 if(sbt_field_icon0 != "")
966 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
967 if(sbt_field_icon1 != "")
968 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
969 if(sbt_field_icon2 != "")
970 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
971 pos.x -= sbt_field_size[i] + hud_fontsize.x;
976 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
979 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
982 vector h_pos = item_pos;
983 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
985 bool complete = (this_team == NUM_SPECTATOR);
988 if((sbt_highlight) && (!(pl_number % 2)))
989 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
991 vector pos = item_pos;
992 pos.x += hud_fontsize.x * 0.5;
993 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
995 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
997 width_limit -= stringwidth("...", false, hud_fontsize);
998 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
999 static float max_name_width = 0;
1001 float fieldsize = 0;
1002 float min_fieldsize = 0;
1003 float fieldpadding = hud_fontsize.x * 0.25;
1004 if(this_team == NUM_SPECTATOR)
1006 if(autocvar_hud_panel_scoreboard_spectators_showping)
1007 min_fieldsize = stringwidth("999", false, hud_fontsize);
1009 else if(autocvar_hud_panel_scoreboard_others_showscore)
1010 min_fieldsize = stringwidth("99", false, hud_fontsize);
1011 for(i = 0; pl; pl = pl.sort_next)
1013 if(pl.team != this_team)
1015 if(pl == ignored_pl)
1019 if(this_team == NUM_SPECTATOR)
1021 if(autocvar_hud_panel_scoreboard_spectators_showping)
1022 field = Scoreboard_GetField(pl, SP_PING);
1024 else if(autocvar_hud_panel_scoreboard_others_showscore)
1025 field = Scoreboard_GetField(pl, SP_SCORE);
1027 string str = entcs_GetName(pl.sv_entnum);
1028 if (autocvar_hud_panel_scoreboard_playerid)
1029 str = Scoreboard_AddPlayerId(str, pl);
1030 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1031 float column_width = stringwidth(str, true, hud_fontsize);
1032 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1034 if(column_width > max_name_width)
1035 max_name_width = column_width;
1036 column_width = max_name_width;
1040 fieldsize = stringwidth(field, false, hud_fontsize);
1041 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1044 if(pos.x + column_width > width_limit)
1049 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1054 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1055 pos.y += hud_fontsize.y * 1.25;
1059 vector name_pos = pos;
1060 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1061 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1062 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1065 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1066 h_size.y = hud_fontsize.y;
1067 vector field_pos = pos;
1068 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1069 field_pos.x += column_width - h_size.x;
1071 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1072 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1073 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1077 h_size.x = column_width + hud_fontsize.x * 0.25;
1078 h_size.y = hud_fontsize.y;
1079 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1081 pos.x += column_width;
1082 pos.x += hud_fontsize.x;
1084 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1087 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1089 int max_players = 999;
1090 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1092 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1095 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1096 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1097 height /= team_count;
1100 height -= panel_bg_padding * 2; // - padding
1101 max_players = floor(height / (hud_fontsize.y * 1.25));
1102 if(max_players <= 1)
1104 if(max_players == tm.team_size)
1109 entity me = playerslots[current_player];
1111 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1112 panel_size.y += panel_bg_padding * 2;
1115 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1116 if(panel.current_panel_bg != "0")
1117 end_pos.y += panel_bg_border * 2;
1119 if(panel_bg_padding)
1121 panel_pos += '1 1 0' * panel_bg_padding;
1122 panel_size -= '2 2 0' * panel_bg_padding;
1126 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1130 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1132 pos.y += 1.25 * hud_fontsize.y;
1135 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1137 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1140 // print header row and highlight columns
1141 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1143 // fill the table and draw the rows
1144 bool is_self = false;
1145 bool self_shown = false;
1147 for(pl = players.sort_next; pl; pl = pl.sort_next)
1149 if(pl.team != tm.team)
1151 if(i == max_players - 2 && pl != me)
1153 if(!self_shown && me.team == tm.team)
1155 Scoreboard_DrawItem(pos, rgb, me, true, i);
1157 pos.y += 1.25 * hud_fontsize.y;
1161 if(i >= max_players - 1)
1163 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1166 is_self = (pl.sv_entnum == current_player);
1167 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1170 pos.y += 1.25 * hud_fontsize.y;
1174 panel_size.x += panel_bg_padding * 2; // restore initial width
1178 bool Scoreboard_WouldDraw()
1180 if (MUTATOR_CALLHOOK(DrawScoreboard))
1182 else if (QuickMenu_IsOpened())
1184 else if (HUD_Radar_Clickable())
1186 else if (scoreboard_showscores)
1188 else if (intermission == 1)
1190 else if (intermission == 2)
1192 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1193 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1197 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1202 float average_accuracy;
1203 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1205 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1207 WepSet weapons_stat = WepSet_GetFromStat();
1208 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1209 int disownedcnt = 0;
1211 FOREACH(Weapons, it != WEP_Null, {
1212 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1214 WepSet set = it.m_wepset;
1215 if(it.spawnflags & WEP_TYPE_OTHER)
1220 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1222 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1229 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1230 if (weapon_cnt <= 0) return pos;
1233 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1235 int columnns = ceil(weapon_cnt / rows);
1237 float weapon_height = 29;
1238 float height = hud_fontsize.y + weapon_height;
1240 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);
1241 pos.y += 1.25 * hud_fontsize.y;
1242 if(panel.current_panel_bg != "0")
1243 pos.y += panel_bg_border;
1246 panel_size.y = height * rows;
1247 panel_size.y += panel_bg_padding * 2;
1249 float panel_bg_alpha_save = panel_bg_alpha;
1250 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1252 panel_bg_alpha = panel_bg_alpha_save;
1254 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1255 if(panel.current_panel_bg != "0")
1256 end_pos.y += panel_bg_border * 2;
1258 if(panel_bg_padding)
1260 panel_pos += '1 1 0' * panel_bg_padding;
1261 panel_size -= '2 2 0' * panel_bg_padding;
1265 vector tmp = panel_size;
1267 float weapon_width = tmp.x / columnns / rows;
1270 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1274 // column highlighting
1275 for (int i = 0; i < columnns; ++i)
1277 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);
1280 for (int i = 0; i < rows; ++i)
1281 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1284 average_accuracy = 0;
1285 int weapons_with_stats = 0;
1287 pos.x += weapon_width / 2;
1289 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1292 Accuracy_LoadColors();
1294 float oldposx = pos.x;
1298 FOREACH(Weapons, it != WEP_Null, {
1299 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1301 WepSet set = it.m_wepset;
1302 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1304 if (it.spawnflags & WEP_TYPE_OTHER)
1308 if (weapon_stats >= 0)
1309 weapon_alpha = sbt_fg_alpha;
1311 weapon_alpha = 0.2 * sbt_fg_alpha;
1314 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1316 if (weapon_stats >= 0) {
1317 weapons_with_stats += 1;
1318 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1321 s = sprintf("%d%%", weapon_stats * 100);
1324 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1326 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1327 rgb = Accuracy_GetColor(weapon_stats);
1329 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1331 tmpos.x += weapon_width * rows;
1332 pos.x += weapon_width * rows;
1333 if (rows == 2 && column == columnns - 1) {
1341 if (weapons_with_stats)
1342 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1344 panel_size.x += panel_bg_padding * 2; // restore initial width
1349 .bool uninteresting;
1350 STATIC_INIT(default_order_items_label)
1352 IL_EACH(default_order_items, true, {
1353 if(!(it.instanceOfPowerup
1354 || it == ITEM_HealthMega || it == ITEM_HealthBig
1355 || it == ITEM_ArmorMega || it == ITEM_ArmorBig
1358 it.uninteresting = true;
1363 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1365 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1367 int disowned_cnt = 0;
1368 int uninteresting_cnt = 0;
1369 IL_EACH(default_order_items, true, {
1370 int q = g_inventory.inv_items[it.m_id];
1371 //q = 1; // debug: display all items
1372 if (autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting)
1373 ++uninteresting_cnt;
1377 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1378 int n = items_cnt - disowned_cnt;
1379 if (n <= 0) return pos;
1381 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1382 int columnns = max(6, ceil(n / rows));
1385 float fontsize = height * 1/3;
1386 float item_height = height * 2/3;
1388 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1389 pos.y += 1.25 * hud_fontsize.y;
1390 if(panel.current_panel_bg != "0")
1391 pos.y += panel_bg_border;
1394 panel_size.y = height * rows;
1395 panel_size.y += panel_bg_padding * 2;
1397 float panel_bg_alpha_save = panel_bg_alpha;
1398 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1400 panel_bg_alpha = panel_bg_alpha_save;
1402 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1403 if(panel.current_panel_bg != "0")
1404 end_pos.y += panel_bg_border * 2;
1406 if(panel_bg_padding)
1408 panel_pos += '1 1 0' * panel_bg_padding;
1409 panel_size -= '2 2 0' * panel_bg_padding;
1413 vector tmp = panel_size;
1415 float item_width = tmp.x / columnns / rows;
1418 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1422 // column highlighting
1423 for (int i = 0; i < columnns; ++i)
1425 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);
1428 for (int i = 0; i < rows; ++i)
1429 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);
1433 pos.x += item_width / 2;
1435 float oldposx = pos.x;
1439 IL_EACH(default_order_items, !(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting), {
1440 int n = g_inventory.inv_items[it.m_id];
1441 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1442 if (n <= 0) continue;
1443 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);
1445 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1446 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);
1447 tmpos.x += item_width * rows;
1448 pos.x += item_width * rows;
1449 if (rows == 2 && column == columnns - 1) {
1457 panel_size.x += panel_bg_padding * 2; // restore initial width
1462 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1464 pos.x += hud_fontsize.x * 0.25;
1465 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1466 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1467 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1469 pos.y += hud_fontsize.y;
1474 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1475 float stat_secrets_found, stat_secrets_total;
1476 float stat_monsters_killed, stat_monsters_total;
1480 // get monster stats
1481 stat_monsters_killed = STAT(MONSTERS_KILLED);
1482 stat_monsters_total = STAT(MONSTERS_TOTAL);
1484 // get secrets stats
1485 stat_secrets_found = STAT(SECRETS_FOUND);
1486 stat_secrets_total = STAT(SECRETS_TOTAL);
1488 // get number of rows
1489 if(stat_secrets_total)
1491 if(stat_monsters_total)
1494 // if no rows, return
1498 // draw table header
1499 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1500 pos.y += 1.25 * hud_fontsize.y;
1501 if(panel.current_panel_bg != "0")
1502 pos.y += panel_bg_border;
1505 panel_size.y = hud_fontsize.y * rows;
1506 panel_size.y += panel_bg_padding * 2;
1509 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1510 if(panel.current_panel_bg != "0")
1511 end_pos.y += panel_bg_border * 2;
1513 if(panel_bg_padding)
1515 panel_pos += '1 1 0' * panel_bg_padding;
1516 panel_size -= '2 2 0' * panel_bg_padding;
1520 vector tmp = panel_size;
1523 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1526 if(stat_monsters_total)
1528 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1529 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1533 if(stat_secrets_total)
1535 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1536 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1539 panel_size.x += panel_bg_padding * 2; // restore initial width
1544 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1547 RANKINGS_RECEIVED_CNT = 0;
1548 for (i=RANKINGS_CNT-1; i>=0; --i)
1550 ++RANKINGS_RECEIVED_CNT;
1552 if (RANKINGS_RECEIVED_CNT == 0)
1555 vector hl_rgb = rgb + '0.5 0.5 0.5';
1557 pos.y += hud_fontsize.y;
1558 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1559 pos.y += 1.25 * hud_fontsize.y;
1560 if(panel.current_panel_bg != "0")
1561 pos.y += panel_bg_border;
1566 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1568 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1573 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1575 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1579 float ranksize = 3 * hud_fontsize.x;
1580 float timesize = 5 * hud_fontsize.x;
1581 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1582 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1583 columns = min(columns, RANKINGS_RECEIVED_CNT);
1585 // expand name column to fill the entire row
1586 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1587 namesize += available_space;
1588 columnsize.x += available_space;
1590 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1591 panel_size.y += panel_bg_padding * 2;
1595 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1596 if(panel.current_panel_bg != "0")
1597 end_pos.y += panel_bg_border * 2;
1599 if(panel_bg_padding)
1601 panel_pos += '1 1 0' * panel_bg_padding;
1602 panel_size -= '2 2 0' * panel_bg_padding;
1608 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1610 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1612 int column = 0, j = 0;
1613 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1614 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1621 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1622 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1623 else if(!((j + column) & 1) && sbt_highlight)
1624 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1626 str = count_ordinal(i+1);
1627 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1628 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1629 str = ColorTranslateRGB(grecordholder[i]);
1631 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1632 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1634 pos.y += 1.25 * hud_fontsize.y;
1636 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1640 pos.x += panel_size.x / columns;
1641 pos.y = panel_pos.y;
1644 strfree(zoned_name_self);
1646 panel_size.x += panel_bg_padding * 2; // restore initial width
1650 float scoreboard_time;
1651 bool have_weapon_stats;
1652 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1654 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1656 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1659 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1660 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1666 if (!have_weapon_stats)
1668 FOREACH(Weapons, it != WEP_Null, {
1669 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1670 if (weapon_stats >= 0)
1672 have_weapon_stats = true;
1676 if (!have_weapon_stats)
1683 bool have_item_stats;
1684 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1686 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1688 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1691 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1692 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1698 if (!have_item_stats)
1700 IL_EACH(default_order_items, true, {
1701 if (!(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting))
1703 int q = g_inventory.inv_items[it.m_id];
1704 //q = 1; // debug: display all items
1707 have_item_stats = true;
1712 if (!have_item_stats)
1719 void Scoreboard_Draw()
1721 if(!autocvar__hud_configure)
1723 if(!hud_draw_maximized) return;
1725 // frametime checks allow to toggle the scoreboard even when the game is paused
1726 if(scoreboard_active) {
1727 if (scoreboard_fade_alpha == 0)
1728 scoreboard_time = time;
1729 if(hud_configure_menu_open == 1)
1730 scoreboard_fade_alpha = 1;
1731 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1732 if (scoreboard_fadeinspeed && frametime)
1733 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1735 scoreboard_fade_alpha = 1;
1736 if(hud_fontsize_str != autocvar_hud_fontsize)
1738 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1739 Scoreboard_initFieldSizes();
1740 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1744 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1745 if (scoreboard_fadeoutspeed && frametime)
1746 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1748 scoreboard_fade_alpha = 0;
1751 if (!scoreboard_fade_alpha)
1753 scoreboard_acc_fade_alpha = 0;
1754 scoreboard_itemstats_fade_alpha = 0;
1759 scoreboard_fade_alpha = 0;
1761 if (autocvar_hud_panel_scoreboard_dynamichud)
1764 HUD_Scale_Disable();
1766 if(scoreboard_fade_alpha <= 0)
1768 panel_fade_alpha *= scoreboard_fade_alpha;
1769 HUD_Panel_LoadCvars();
1771 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1772 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1773 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1774 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1775 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1776 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1777 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1779 // don't overlap with con_notify
1780 if(!autocvar__hud_configure)
1781 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1783 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1784 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1785 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1786 panel_size.x = fixed_scoreboard_width;
1788 Scoreboard_UpdatePlayerTeams();
1790 float initial_pos_y = panel_pos.y;
1791 vector pos = panel_pos;
1796 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1798 // Begin of Game Info Section
1799 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1800 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1802 // Game Info: Game Type
1803 str = MapInfo_Type_ToText(gametype);
1804 draw_beginBoldFont();
1805 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);
1808 // Game Info: Game Detail
1809 float tl = STAT(TIMELIMIT);
1810 float fl = STAT(FRAGLIMIT);
1811 float ll = STAT(LEADLIMIT);
1812 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1815 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1816 if(!gametype.m_hidelimits)
1821 str = strcat(str, "^7 / "); // delimiter
1824 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1825 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1826 (teamscores_label(ts_primary) == "fastest") ? "" :
1827 TranslateScoresLabel(teamscores_label(ts_primary))));
1831 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1832 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1833 (scores_label(ps_primary) == "fastest") ? "" :
1834 TranslateScoresLabel(scores_label(ps_primary))));
1839 if(tl > 0 || fl > 0)
1842 if (ll_and_fl && fl > 0)
1843 str = strcat(str, "^7 & ");
1845 str = strcat(str, "^7 / ");
1850 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1851 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1852 (teamscores_label(ts_primary) == "fastest") ? "" :
1853 TranslateScoresLabel(teamscores_label(ts_primary))));
1857 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1858 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1859 (scores_label(ps_primary) == "fastest") ? "" :
1860 TranslateScoresLabel(scores_label(ps_primary))));
1865 pos.y += sb_gameinfo_type_fontsize.y;
1866 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
1868 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1869 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1870 // End of Game Info Section
1872 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1873 if(panel.current_panel_bg != "0")
1874 pos.y += panel_bg_border;
1876 // Draw the scoreboard
1877 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1880 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1884 vector panel_bg_color_save = panel_bg_color;
1885 vector team_score_baseoffset;
1886 vector team_size_baseoffset;
1887 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1889 // put team score to the left of scoreboard (and team size to the right)
1890 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1891 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1892 if(panel.current_panel_bg != "0")
1894 team_score_baseoffset.x -= panel_bg_border;
1895 team_size_baseoffset.x += panel_bg_border;
1900 // put team score to the right of scoreboard (and team size to the left)
1901 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1902 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1903 if(panel.current_panel_bg != "0")
1905 team_score_baseoffset.x += panel_bg_border;
1906 team_size_baseoffset.x -= panel_bg_border;
1910 int team_size_total = 0;
1911 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1913 // calculate team size total (sum of all team sizes)
1914 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1915 if(tm.team != NUM_SPECTATOR)
1916 team_size_total += tm.team_size;
1919 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1921 if(tm.team == NUM_SPECTATOR)
1926 draw_beginBoldFont();
1927 vector rgb = Team_ColorRGB(tm.team);
1928 str = ftos(tm.(teamscores(ts_primary)));
1929 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1931 // team score on the left (default)
1932 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1936 // team score on the right
1937 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1939 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1941 // team size (if set to show on the side)
1942 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1944 // calculate the starting position for the whole team size info string
1945 str = sprintf("%d/%d", tm.team_size, team_size_total);
1946 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1948 // team size on the left
1949 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1953 // team size on the right
1954 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1956 str = sprintf("%d", tm.team_size);
1957 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1958 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1959 str = sprintf("/%d", team_size_total);
1960 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1964 // secondary score, e.g. keyhunt
1965 if(ts_primary != ts_secondary)
1967 str = ftos(tm.(teamscores(ts_secondary)));
1968 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1971 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1976 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1979 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1982 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1983 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1984 else if(panel_bg_color_team > 0)
1985 panel_bg_color = rgb * panel_bg_color_team;
1987 panel_bg_color = rgb;
1988 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1990 panel_bg_color = panel_bg_color_save;
1994 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1995 if(tm.team != NUM_SPECTATOR)
1998 // display it anyway
1999 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2002 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2003 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2004 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2005 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2007 if(MUTATOR_CALLHOOK(ShowRankings)) {
2008 string ranktitle = M_ARGV(0, string);
2009 if(race_speedaward) {
2010 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);
2011 pos.y += 1.25 * hud_fontsize.y;
2013 if(race_speedaward_alltimebest) {
2014 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);
2015 pos.y += 1.25 * hud_fontsize.y;
2017 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2020 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2023 for(pl = players.sort_next; pl; pl = pl.sort_next)
2025 if(pl.team == NUM_SPECTATOR)
2027 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2028 if(tm.team == NUM_SPECTATOR)
2030 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2031 draw_beginBoldFont();
2032 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2034 pos.y += 1.25 * hud_fontsize.y;
2036 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2037 pos.y += 1.25 * hud_fontsize.y;
2044 // print information about respawn status
2045 float respawn_time = STAT(RESPAWN_TIME);
2049 if(respawn_time < 0)
2051 // a negative number means we are awaiting respawn, time value is still the same
2052 respawn_time *= -1; // remove mark now that we checked it
2054 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2055 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2057 str = sprintf(_("^1Respawning in ^3%s^1..."),
2058 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2059 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2061 count_seconds(ceil(respawn_time - time))
2065 else if(time < respawn_time)
2067 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2068 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2069 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2071 count_seconds(ceil(respawn_time - time))
2075 else if(time >= respawn_time)
2076 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2078 pos.y += 1.2 * hud_fontsize.y;
2079 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2082 pos.y += 2 * hud_fontsize.y;
2083 if (scoreboard_fade_alpha < 1)
2084 scoreboard_bottom = initial_pos_y + (pos.y - initial_pos_y) * scoreboard_fade_alpha;
2085 else if (pos.y != scoreboard_bottom)
2087 if (pos.y > scoreboard_bottom)
2088 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - initial_pos_y));
2090 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - initial_pos_y));