1 #include "scoreboard.qh"
3 #include <client/autocvars.qh>
4 #include <client/draw.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_bg_teams_color_team");
34 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
35 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
38 const int MAX_SBT_FIELDS = MAX_SCORE;
40 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
41 float sbt_field_size[MAX_SBT_FIELDS + 1];
42 string sbt_field_title[MAX_SBT_FIELDS + 1];
45 string autocvar_hud_fontsize;
46 string hud_fontsize_str;
49 vector duel_score_fontsize;
50 vector duel_name_fontsize;
51 vector duel_score_size;
52 vector team_score_fontsize;
53 vector team_name_fontsize;
54 vector team_score_size;
59 float sbt_fg_alpha_self;
61 float sbt_highlight_alpha;
62 float sbt_highlight_alpha_self;
64 // provide basic panel cvars to old clients
65 // TODO remove them after a future release (0.8.2+)
66 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
67 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
68 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
69 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
70 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
71 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
72 noref string autocvar_hud_panel_scoreboard_bg_border = "";
73 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
75 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
76 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
77 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
78 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
79 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
80 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
81 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
82 bool autocvar_hud_panel_scoreboard_table_highlight = true;
83 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
84 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
85 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
86 float autocvar_hud_panel_scoreboard_namesize = 15;
87 float autocvar_hud_panel_scoreboard_team_size_position = 0;
89 bool autocvar_hud_panel_scoreboard_accuracy = true;
90 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
91 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
92 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
93 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
95 bool autocvar_hud_panel_scoreboard_dynamichud = false;
97 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
98 bool autocvar_hud_panel_scoreboard_others_showscore = true;
99 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
100 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
101 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
103 int average_ping[NUM_TEAMS];
105 // mode 0: returns translated label
106 // mode 1: prints name and description of all the labels
107 string Label_getInfo(string label, int mode)
110 label = "bckills"; // first case in the switch
114 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
115 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
116 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")));
117 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
118 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
119 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
120 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
121 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
122 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
123 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
124 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
125 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
126 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
127 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
128 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
129 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
130 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
131 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
132 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
133 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
134 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
135 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
136 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
137 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
138 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
139 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
140 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
141 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")));
142 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
143 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
144 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
145 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
146 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
147 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
148 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
149 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
150 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
151 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
152 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
153 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
154 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
155 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
156 default: return label;
161 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
162 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
164 void Scoreboard_InitScores()
168 ps_primary = ps_secondary = NULL;
169 ts_primary = ts_secondary = -1;
170 FOREACH(Scores, true, {
171 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
172 if(f == SFL_SORT_PRIO_PRIMARY)
174 if(f == SFL_SORT_PRIO_SECONDARY)
177 if(ps_secondary == NULL)
178 ps_secondary = ps_primary;
180 for(i = 0; i < MAX_TEAMSCORE; ++i)
182 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
183 if(f == SFL_SORT_PRIO_PRIMARY)
185 if(f == SFL_SORT_PRIO_SECONDARY)
188 if(ts_secondary == -1)
189 ts_secondary = ts_primary;
191 Cmd_Scoreboard_SetFields(0);
195 void Scoreboard_UpdatePlayerTeams()
199 for(pl = players.sort_next; pl; pl = pl.sort_next)
202 int Team = entcs_GetScoreTeam(pl.sv_entnum);
203 if(SetTeam(pl, Team))
206 Scoreboard_UpdatePlayerPos(pl);
210 pl = players.sort_next;
215 print(strcat("PNUM: ", ftos(num), "\n"));
220 int Scoreboard_CompareScore(int vl, int vr, int f)
222 TC(int, vl); TC(int, vr); TC(int, f);
223 if(f & SFL_ZERO_IS_WORST)
225 if(vl == 0 && vr != 0)
227 if(vl != 0 && vr == 0)
231 return IS_INCREASING(f);
233 return IS_DECREASING(f);
237 float Scoreboard_ComparePlayerScores(entity left, entity right)
240 vl = entcs_GetTeam(left.sv_entnum);
241 vr = entcs_GetTeam(right.sv_entnum);
253 if(vl == NUM_SPECTATOR)
255 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
257 if(!left.gotscores && right.gotscores)
262 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
266 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
270 FOREACH(Scores, true, {
271 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
272 if (r >= 0) return r;
275 if (left.sv_entnum < right.sv_entnum)
281 void Scoreboard_UpdatePlayerPos(entity player)
284 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
286 SORT_SWAP(player, ent);
288 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
290 SORT_SWAP(ent, player);
294 float Scoreboard_CompareTeamScores(entity left, entity right)
298 if(left.team == NUM_SPECTATOR)
300 if(right.team == NUM_SPECTATOR)
303 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
307 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
311 for(i = 0; i < MAX_TEAMSCORE; ++i)
313 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
318 if (left.team < right.team)
324 void Scoreboard_UpdateTeamPos(entity Team)
327 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
329 SORT_SWAP(Team, ent);
331 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
333 SORT_SWAP(ent, Team);
337 void Cmd_Scoreboard_Help()
339 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
340 LOG_HELP(_("Usage:"));
341 LOG_HELP("^2scoreboard_columns_set ^3default");
342 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
343 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
344 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
345 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
346 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
347 LOG_HELP(_("The following field names are recognized (case insensitive):"));
353 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
354 "of game types, then a slash, to make the field show up only in these\n"
355 "or in all but these game types. You can also specify 'all' as a\n"
356 "field to show all fields available for the current game mode."));
359 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
360 "include/exclude ALL teams/noteams game modes."));
363 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
364 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
365 "right of the vertical bar aligned to the right."));
366 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
367 "other gamemodes except DM."));
370 // NOTE: adding a gametype with ? to not warn for an optional field
371 // make sure it's excluded in a previous exclusive rule, if any
372 // otherwise the previous exclusive rule warns anyway
373 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
374 #define SCOREBOARD_DEFAULT_COLUMNS \
375 "ping pl fps name |" \
376 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
377 " -teams,lms/deaths +ft,tdm/deaths" \
379 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
380 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
381 " +tdm,ft,dom,ons,as/teamkills"\
382 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
383 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
384 " +lms/lives +lms/rank" \
385 " +kh/kckills +kh/losses +kh/caps" \
386 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
387 " +as/objectives +nb/faults +nb/goals" \
388 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
389 " +dom/ticks +dom/takes" \
390 " -lms,rc,cts,inv,nb/score"
392 void Cmd_Scoreboard_SetFields(int argc)
397 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
401 return; // do nothing, we don't know gametype and scores yet
403 // sbt_fields uses strunzone on the titles!
404 if(!sbt_field_title[0])
405 for(i = 0; i < MAX_SBT_FIELDS; ++i)
406 sbt_field_title[i] = strzone("(null)");
408 // TODO: re enable with gametype dependant cvars?
409 if(argc < 3) // no arguments provided
410 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
413 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
417 if(argv(2) == "default" || argv(2) == "expand_default")
419 if(argv(2) == "expand_default")
420 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
421 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
423 else if(argv(2) == "all")
425 string s = "ping pl name |"; // scores without a label
426 FOREACH(Scores, true, {
428 if(it != ps_secondary)
429 if(scores_label(it) != "")
430 s = strcat(s, " ", scores_label(it));
432 if(ps_secondary != ps_primary)
433 s = strcat(s, " ", scores_label(ps_secondary));
434 s = strcat(s, " ", scores_label(ps_primary));
435 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
442 hud_fontsize = HUD_GetFontsize("hud_fontsize");
444 duel_score_fontsize = hud_fontsize * 3;
445 duel_name_fontsize = hud_fontsize * 1.5;
446 duel_score_size = vec2(duel_score_fontsize.x * 1.5, duel_score_fontsize.y * 1.25);
448 team_score_fontsize = hud_fontsize * 2;
449 team_name_fontsize = hud_fontsize * 1.5;
450 team_score_size = vec2(team_score_fontsize.x * 1.5, team_score_fontsize.y * 1.25);
452 for(i = 1; i < argc - 1; ++i)
455 bool nocomplain = false;
456 if(substring(str, 0, 1) == "?")
459 str = substring(str, 1, strlen(str) - 1);
462 slash = strstrofs(str, "/", 0);
465 pattern = substring(str, 0, slash);
466 str = substring(str, slash + 1, strlen(str) - (slash + 1));
468 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
472 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
473 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
474 str = strtolower(str);
479 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
480 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
481 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
482 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
483 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
484 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
485 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
486 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
487 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
488 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
491 FOREACH(Scores, true, {
492 if (str == strtolower(scores_label(it))) {
494 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
504 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
508 sbt_field[sbt_num_fields] = j;
511 if(j == ps_secondary)
512 have_secondary = true;
517 if(sbt_num_fields >= MAX_SBT_FIELDS)
521 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
523 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
524 have_secondary = true;
525 if(ps_primary == ps_secondary)
526 have_secondary = true;
527 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
529 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
533 strunzone(sbt_field_title[sbt_num_fields]);
534 for(i = sbt_num_fields; i > 0; --i)
536 sbt_field_title[i] = sbt_field_title[i-1];
537 sbt_field_size[i] = sbt_field_size[i-1];
538 sbt_field[i] = sbt_field[i-1];
540 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
541 sbt_field[0] = SP_NAME;
543 LOG_INFO("fixed missing field 'name'");
547 strunzone(sbt_field_title[sbt_num_fields]);
548 for(i = sbt_num_fields; i > 1; --i)
550 sbt_field_title[i] = sbt_field_title[i-1];
551 sbt_field_size[i] = sbt_field_size[i-1];
552 sbt_field[i] = sbt_field[i-1];
554 sbt_field_title[1] = strzone("|");
555 sbt_field[1] = SP_SEPARATOR;
556 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
558 LOG_INFO("fixed missing field '|'");
561 else if(!have_separator)
563 strcpy(sbt_field_title[sbt_num_fields], "|");
564 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
565 sbt_field[sbt_num_fields] = SP_SEPARATOR;
567 LOG_INFO("fixed missing field '|'");
571 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
572 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
573 sbt_field[sbt_num_fields] = ps_secondary;
575 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
579 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
580 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
581 sbt_field[sbt_num_fields] = ps_primary;
583 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
587 sbt_field[sbt_num_fields] = SP_END;
591 vector sbt_field_rgb;
592 string sbt_field_icon0;
593 string sbt_field_icon1;
594 string sbt_field_icon2;
595 vector sbt_field_icon0_rgb;
596 vector sbt_field_icon1_rgb;
597 vector sbt_field_icon2_rgb;
598 string Scoreboard_GetName(entity pl)
600 if(ready_waiting && pl.ready)
602 sbt_field_icon0 = "gfx/scoreboard/player_ready";
606 int f = entcs_GetClientColors(pl.sv_entnum);
608 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
609 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
610 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
611 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
612 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
617 int ccode = entcs_GetCountryCode(pl.sv_entnum);
619 sbt_field_icon0 = strcat("gfx/flags/", ftos(ccode));
622 return entcs_GetName(pl.sv_entnum);
625 vector getPingColor(float f)
628 // 20-80 range is green
629 return '0 1 0' + '1 0 1' * max(0, min(60, f-20)) / 60;
631 // 80-300 range is red
632 return '1 1 1' - '0 1 1' * max(0, min(220, f-80)) / 220;
635 string Scoreboard_GetField(entity pl, PlayerScoreField field)
637 float tmp, num, denom;
640 sbt_field_rgb = '1 1 1';
641 sbt_field_icon0 = "";
642 sbt_field_icon1 = "";
643 sbt_field_icon2 = "";
644 sbt_field_icon0_rgb = '1 1 1';
645 sbt_field_icon1_rgb = '1 1 1';
646 sbt_field_icon2_rgb = '1 1 1';
651 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
652 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
656 sbt_field_rgb = getPingColor(f);
659 //return ftos(pl.team);
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 return Scoreboard_GetName(pl);
679 f = pl.(scores(SP_KILLS));
680 f -= pl.(scores(SP_SUICIDES));
684 num = pl.(scores(SP_KILLS));
685 denom = pl.(scores(SP_DEATHS));
688 sbt_field_rgb = '0 1 0';
689 str = sprintf("%d", num);
690 } else if(num <= 0) {
691 sbt_field_rgb = '1 0 0';
692 str = sprintf("%.1f", num/denom);
694 str = sprintf("%.1f", num/denom);
698 f = pl.(scores(SP_KILLS));
699 f -= pl.(scores(SP_DEATHS));
702 sbt_field_rgb = '0 1 0';
704 sbt_field_rgb = '1 1 1';
706 sbt_field_rgb = '1 0 0';
712 float elo = pl.(scores(SP_ELO));
714 case -1: return "...";
715 case -2: return _("N/A");
716 default: return ftos(elo);
722 float fps = pl.(scores(SP_FPS));
725 sbt_field_rgb = '1 1 1';
726 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
728 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
729 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
733 case SP_DMG: case SP_DMGTAKEN:
734 return sprintf("%.1f k", pl.(scores(field)) / 1000);
736 default: case SP_SCORE:
737 tmp = pl.(scores(field));
738 f = scores_flags(field);
739 if(field == ps_primary)
740 sbt_field_rgb = '1 1 0';
741 else if(field == ps_secondary)
742 sbt_field_rgb = '0 1 1';
744 sbt_field_rgb = '1 1 1';
745 return ScoreString(f, tmp);
750 float sbt_fixcolumnwidth_len;
751 float sbt_fixcolumnwidth_iconlen;
752 float sbt_fixcolumnwidth_marginlen;
754 string Scoreboard_FixColumnWidth(int i, string str)
760 sbt_fixcolumnwidth_iconlen = 0;
762 if(sbt_field_icon0 != "")
764 sz = draw_getimagesize(sbt_field_icon0);
766 if(sbt_fixcolumnwidth_iconlen < f)
767 sbt_fixcolumnwidth_iconlen = f;
770 if(sbt_field_icon1 != "")
772 sz = draw_getimagesize(sbt_field_icon1);
774 if(sbt_fixcolumnwidth_iconlen < f)
775 sbt_fixcolumnwidth_iconlen = f;
778 if(sbt_field_icon2 != "")
780 sz = draw_getimagesize(sbt_field_icon2);
782 if(sbt_fixcolumnwidth_iconlen < f)
783 sbt_fixcolumnwidth_iconlen = f;
786 if(sbt_fixcolumnwidth_iconlen != 0)
788 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
789 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
792 sbt_fixcolumnwidth_marginlen = 0;
794 if(sbt_field[i] == SP_NAME) // name gets all remaining space
797 float remaining_space = 0;
798 for(j = 0; j < sbt_num_fields; ++j)
800 if (sbt_field[i] != SP_SEPARATOR)
801 remaining_space += sbt_field_size[j] + hud_fontsize.x;
802 sbt_field_size[i] = panel_size.x - remaining_space;
804 if (sbt_fixcolumnwidth_iconlen != 0)
805 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
806 float namesize = panel_size.x - remaining_space;
807 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
808 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
810 max_namesize = vid_conwidth - remaining_space;
813 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
815 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
816 if(sbt_field_size[i] < f)
817 sbt_field_size[i] = f;
822 void Scoreboard_initFieldSizes()
824 for(int i = 0; i < sbt_num_fields; ++i)
826 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
827 Scoreboard_FixColumnWidth(i, "");
831 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players, int team)
836 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)
847 vector text_offset_center = '0 0 0';
849 if(sbt_field[i] == SP_PING && teamplay) {
850 title_str = sprintf("(%d)", average_ping[Team_TeamToIndex(team) - 1]);
851 title_rgb = getPingColor(average_ping[Team_TeamToIndex(team) - 1]);
852 text_offset_center.x = sbt_field_size[i] - stringwidth(title_str, false, hud_fontsize);
854 title_str = sbt_field_title[i];
855 title_rgb = rgb * 1.5;
858 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
861 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
862 drawstring(pos + text_offset + text_offset_center, title_str, hud_fontsize, title_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
863 pos.x += column_dim.x;
865 if(sbt_field[i] == SP_SEPARATOR)
867 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
868 for(i = sbt_num_fields - 1; i > 0; --i)
870 if(sbt_field[i] == SP_SEPARATOR)
873 pos.x -= sbt_field_size[i];
878 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
879 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
882 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
883 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
884 pos.x -= hud_fontsize.x;
889 pos.y += 1.25 * hud_fontsize.y;
893 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
895 TC(bool, is_self); TC(int, pl_number);
897 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
899 vector h_pos = item_pos;
900 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
901 // alternated rows highlighting
903 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
904 else if((sbt_highlight) && (!(pl_number % 2)))
905 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
907 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
909 vector pos = item_pos;
910 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
912 drawstring(pos+eX*(panel_size.x+.5*hud_fontsize.x)+eY, "\xE2\x97\x80", vec2(hud_fontsize.x, hud_fontsize.y), rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
914 pos.x += hud_fontsize.x * 0.5;
915 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
916 vector tmp = '0 0 0';
918 PlayerScoreField field;
919 for(i = 0; i < sbt_num_fields; ++i)
921 field = sbt_field[i];
922 if(field == SP_SEPARATOR)
925 if(is_spec && field != SP_NAME && field != SP_PING) {
926 pos.x += sbt_field_size[i] + hud_fontsize.x;
929 str = Scoreboard_GetField(pl, field);
930 str = Scoreboard_FixColumnWidth(i, str);
932 pos.x += sbt_field_size[i] + hud_fontsize.x;
934 if(field == SP_NAME) {
935 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
936 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
938 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
939 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
942 tmp.x = sbt_field_size[i] + hud_fontsize.x;
943 if(sbt_field_icon0 != "")
944 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
945 if(sbt_field_icon1 != "")
946 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
947 if(sbt_field_icon2 != "")
948 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
951 if(sbt_field[i] == SP_SEPARATOR)
953 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
954 for(i = sbt_num_fields-1; i > 0; --i)
956 field = sbt_field[i];
957 if(field == SP_SEPARATOR)
960 if(is_spec && field != SP_NAME && field != SP_PING) {
961 pos.x -= sbt_field_size[i] + hud_fontsize.x;
965 str = Scoreboard_GetField(pl, field);
966 str = Scoreboard_FixColumnWidth(i, str);
968 if(field == SP_NAME) {
969 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
970 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
972 tmp.x = sbt_fixcolumnwidth_len;
973 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
976 tmp.x = sbt_field_size[i];
977 if(sbt_field_icon0 != "")
978 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
979 if(sbt_field_icon1 != "")
980 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
981 if(sbt_field_icon2 != "")
982 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
983 pos.x -= sbt_field_size[i] + hud_fontsize.x;
988 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
991 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
994 vector h_pos = item_pos;
995 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
997 bool complete = (this_team == NUM_SPECTATOR);
1000 if((sbt_highlight) && (!(pl_number % 2)))
1001 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1003 vector pos = item_pos;
1004 pos.x += hud_fontsize.x * 0.5;
1005 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1007 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1009 width_limit -= stringwidth("...", false, hud_fontsize);
1010 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1011 static float max_name_width = 0;
1013 float fieldsize = 0;
1014 float min_fieldsize = 0;
1015 float fieldpadding = hud_fontsize.x * 0.25;
1016 if(this_team == NUM_SPECTATOR)
1018 if(autocvar_hud_panel_scoreboard_spectators_showping)
1019 min_fieldsize = stringwidth("999", false, hud_fontsize);
1021 else if(autocvar_hud_panel_scoreboard_others_showscore)
1022 min_fieldsize = stringwidth("99", false, hud_fontsize);
1023 for(i = 0; pl; pl = pl.sort_next)
1025 if(pl.team != this_team)
1027 if(pl == ignored_pl)
1031 if(this_team == NUM_SPECTATOR)
1033 if(autocvar_hud_panel_scoreboard_spectators_showping)
1034 field = Scoreboard_GetField(pl, SP_PING);
1036 else if(autocvar_hud_panel_scoreboard_others_showscore)
1037 field = Scoreboard_GetField(pl, SP_SCORE);
1039 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
1040 float column_width = stringwidth(str, true, hud_fontsize);
1041 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1043 if(column_width > max_name_width)
1044 max_name_width = column_width;
1045 column_width = max_name_width;
1049 fieldsize = stringwidth(field, false, hud_fontsize);
1050 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1053 if(pos.x + column_width > width_limit)
1058 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1063 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1064 pos.y += hud_fontsize.y * 1.25;
1068 vector name_pos = pos;
1069 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1070 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1071 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1074 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1075 h_size.y = hud_fontsize.y;
1076 vector field_pos = pos;
1077 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1078 field_pos.x += column_width - h_size.x;
1080 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1081 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1082 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1086 h_size.x = column_width + hud_fontsize.x * 0.25;
1087 h_size.y = hud_fontsize.y;
1088 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
1090 pos.x += column_width;
1091 pos.x += hud_fontsize.x;
1093 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1096 vector Scoreboard_DrawMedal(vector pos, string icon, float height, float number)
1098 if(!number) return pos;
1099 total_medals += number;
1101 vector tmp_sz, tmp_sz2;
1102 tmp_sz = draw_getimagesize(icon);
1103 tmp_sz2 = vec2(height*(tmp_sz.x/tmp_sz.y), height);
1104 string val = ftos(number);
1106 drawpic(pos, icon, tmp_sz2, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1108 pos.x += tmp_sz2.x + hud_fontsize.x * 0.25;
1109 drawstring(pos + eY * ((tmp_sz2.y - hud_fontsize.y) / 2), val, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1111 pos.x += stringwidth(val, false, hud_fontsize) + hud_fontsize.x * 0.5;
1115 vector Scoreboard_Duel_DrawPickup(vector pos, bool skinned, string icon, vector sz, float number, bool invert)
1117 vector tmp_in = pos;
1118 vector tmp_sz, tmp_sz2;
1123 picpath = strcat(hud_skin_path, "/", icon);
1124 if(precache_pic(picpath) == "")
1125 picpath = strcat("gfx/hud/default/", icon);
1130 tmp_sz = draw_getimagesize(picpath);
1131 tmp_sz2 = vec2(sz.y*(tmp_sz.x/tmp_sz.y), sz.y);
1133 tmp_in.x = pos.x + ((sz.x - tmp_sz2.x) / 2);
1134 drawpic(tmp_in, picpath, tmp_sz2, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1138 tmp_in.x += tmp_sz2.x + hud_fontsize.x * 0.25;
1140 tmp_in.x -= hud_fontsize.x * 0.25 + hud_fontsize.x;
1142 tmp_in.y += (tmp_sz2.y - hud_fontsize.y) / 2;
1144 ((number == -1) ? "?" : ftos(number)),
1145 hud_fontsize, ((number > 0) ? '1 1 1' : '0.5 0.5 0.5'),
1149 pos.y += sz.y * 1.1;
1153 void Scoreboard_Duel_DrawTable(vector pos, bool invert, entity pl, entity tm)
1155 vector tmp, tmp_in, tmp_sz, tmp_acc;
1158 float average_acc = 0;
1164 // Stop here if there are no scores available
1165 if(pl.team != tm.team) return;
1168 tmp.x += panel_bg_padding;
1169 tmp.y += panel_bg_padding;
1170 panel_size.x -= panel_bg_padding * 2;
1173 // drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", tmp, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1176 if(invert) { tmp.x += panel_size.x; tmp.x -= duel_score_size.x; }
1177 drawfill(tmp, duel_score_size, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1180 tmp_str = ftos(pl.(scores(SP_SCORE)));
1182 tmp_in.x += (duel_score_size.x / 2) - (stringwidth(tmp_str, true, duel_score_fontsize) / 2);
1183 tmp_in.y += (duel_score_size.y / 2) - (duel_score_fontsize.y / 2);
1185 draw_beginBoldFont();
1186 drawstring(tmp_in, tmp_str, duel_score_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1190 tmp_str = Scoreboard_GetField(pl, SP_NAME);
1193 tmp_in.x -= stringwidth_colors(tmp_str, duel_name_fontsize) + duel_name_fontsize.x * 0.5;
1195 tmp_in.x += duel_score_size.x + duel_name_fontsize.x * 0.5;
1196 tmp_in.y += (duel_score_size.y - duel_name_fontsize.y) / 2;
1197 drawcolorcodedstring(tmp_in, tmp_str, duel_name_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1200 if(sbt_field_icon0 != "") {
1201 vector rsz = draw_getimagesize(sbt_field_icon0);
1202 sbt_fixcolumnwidth_iconlen = rsz.x / rsz.y;
1204 tmp_in.x -= hud_fontsize.x * sbt_fixcolumnwidth_iconlen + duel_name_fontsize.x * 0.5;
1206 tmp_in.x += stringwidth_colors(tmp_str, duel_name_fontsize) + duel_name_fontsize.x * 0.5;
1207 tmp_in.y += (duel_name_fontsize.y - hud_fontsize.y) / 2;
1208 drawpic(tmp_in, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1212 float column_width = panel_size.x / 5;
1213 tmp.x = pos.x + panel_bg_padding;
1214 tmp.y += hud_fontsize.y * 3 + hud_fontsize.y;
1219 i = (invert ? 4 : 0);
1220 column_dim = vec2(column_width * 4, hud_fontsize.y);
1222 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("kills", false, hud_fontsize) / 2),
1223 "kills", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1224 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("dmg", false, hud_fontsize) / 2),
1225 "dmg", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1226 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("acc", false, hud_fontsize) / 2),
1227 "acc", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1228 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("hits", false, hud_fontsize) / 2),
1229 "hits", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1230 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("ping", false, hud_fontsize) / 2),
1231 "ping", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1233 tmp.x = pos.x + panel_bg_padding;
1234 tmp.y += hud_fontsize.y;
1237 i = (invert ? 4 : 0);
1239 tmp_str = ftos(pl.(scores(SP_KILLS)));
1240 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1241 tmp_str, hud_fontsize * 1.25, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1243 tmp_str = ftos(pl.(scores(SP_DMG)));
1244 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1245 tmp_str, hud_fontsize * 1.25, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1247 tmp_acc = tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2);
1254 tmp_str = Scoreboard_GetField(pl, SP_PING);
1255 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1256 tmp_str, hud_fontsize * 1.25, sbt_field_rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1258 tmp.x = pos.x + panel_bg_padding;
1259 tmp.y += hud_fontsize.y * 2;
1263 int total_weapons = 0;
1266 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1267 FOREACH(Weapons, it != WEP_Null, {
1268 WepSet set = it.m_wepset;
1269 if (!(weapons_inmap & set))
1271 if (it.spawnflags & WEP_TYPE_OTHER)
1274 int weapon_cnt_fired = pl.accuracy_cnt_fired[i - WEP_FIRST];
1275 int weapon_cnt_hit = pl.accuracy_cnt_hit[i - WEP_FIRST];
1277 if(weapon_cnt_fired)
1278 weapon_acc = floor((weapon_cnt_hit / weapon_cnt_fired) * 100);
1279 average_acc += weapon_acc;
1284 int c = (invert ? 4 : 0);
1286 drawfill(tmp_in + eX * column_width * (invert ? 1 : 0), column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1288 draw_str = ftos(pl.accuracy_frags[i - WEP_FIRST]);
1289 drawstring(tmp_in + eX * column_width * (invert ? c-- : c++) + eX * ((column_width - stringwidth(draw_str, false, hud_fontsize)) / 2),
1290 draw_str, hud_fontsize, (weapon_cnt_fired ? '1 1 1' : '0.5 0.5 0.5'), panel_fg_alpha, DRAWFLAG_NORMAL);
1292 draw_str = ftos(pl.accuracy_hit[i - WEP_FIRST]);
1293 drawstring(tmp_in + eX * column_width * (invert ? c-- : c++) + eX * ((column_width - stringwidth(draw_str, false, hud_fontsize)) / 2),
1294 draw_str, hud_fontsize, (weapon_cnt_fired ? '1 1 1' : '0.5 0.5 0.5'), panel_fg_alpha, DRAWFLAG_NORMAL);
1296 draw_str = sprintf("%d%%", weapon_acc);
1297 drawstring(tmp_in + eX * column_width * (invert ? c-- : c++) + eX * ((column_width - stringwidth(draw_str, false, hud_fontsize)) / 2),
1298 draw_str, hud_fontsize, (weapon_cnt_fired ? '1 1 1' : '0.5 0.5 0.5'), panel_fg_alpha, DRAWFLAG_NORMAL);
1300 draw_str = strcat(ftos(weapon_cnt_hit), " / ", ftos(weapon_cnt_fired));
1301 drawstring(tmp_in + eX * column_width * (invert ? c-- : c++) + eX * (column_width / 2) - eX * stringwidth("36 /", false, hud_fontsize),
1302 draw_str,hud_fontsize, (weapon_cnt_fired ? '1 1 1' : '0.5 0.5 0.5'), panel_fg_alpha, DRAWFLAG_NORMAL);
1306 tmp_in.x = pos.x + panel_size.x - panel_bg_padding - hud_fontsize.x / 2;
1307 drawpic_aspect_skin(tmp_in, it.model2, vec2(50, hud_fontsize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1310 tmp_in.x = pos.x + panel_bg_padding;
1311 tmp_in.y += hud_fontsize.y * 1.25;
1313 if(weapon_cnt_fired)
1317 average_acc = floor((average_acc / total_weapons) + 0.5);
1319 // draw total accuracy now
1320 tmp_str = sprintf("%d%%", average_acc);
1321 drawstring(tmp_acc - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1322 tmp_str, hud_fontsize * 1.25, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1325 vector icon_sz = vec2(column_width, hud_fontsize.y*1.5);
1328 tmp.x += column_width * 4;
1330 drawstring(tmp + eX * ((column_width - stringwidth("medals", false, hud_fontsize)) / 2),
1331 "medals", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1332 tmp.y += hud_fontsize.y * 1.25;
1334 tmp = Scoreboard_Duel_DrawPickup(tmp, false, "gfx/medal/humiliation", icon_sz, pl.(scores(SP_MEDAL_HUMILIATION)), invert);
1335 tmp = Scoreboard_Duel_DrawPickup(tmp, false, "gfx/medal/impressive", icon_sz, pl.(scores(SP_MEDAL_IMPRESSIVE)), invert);
1336 tmp = Scoreboard_Duel_DrawPickup(tmp, false, "gfx/medal/excellent", icon_sz, pl.(scores(SP_MEDAL_EXCELLENT)), invert);
1339 drawstring(tmp + eX * ((column_width - stringwidth("items", false, hud_fontsize)) / 2),
1340 "items", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1341 tmp.y += hud_fontsize.y * 1.25;
1345 it.m_id == ITEM_ArmorMega.m_id ||
1346 it.m_id == ITEM_HealthMega.m_id ||
1347 it.m_id == ITEM_ArmorBig.m_id, {
1348 // If the match isn't over, Only show pickups if we're spectating or they're our own
1349 if(intermission || warmup_stage || spectatee_status || pl.sv_entnum == current_player)
1350 inv_num = inventoryslots[pl.sv_entnum].inv_items[it.m_id];
1351 tmp = Scoreboard_Duel_DrawPickup(tmp, true, it.m_icon, icon_sz, inv_num, invert);
1353 if(it.m_id == REGISTRY_MAX(Items))
1357 vector Scoreboard_MakeDuelTable(vector pos, entity tm, vector rgb, vector bg_size)
1359 vector end_pos = pos;
1360 float screen_half = panel_size.x / 2;
1361 float weapon_margin = hud_fontsize.x;
1363 panel_size.x = screen_half - weapon_margin;
1364 panel_size.y = (duel_score_size.y * 5.5);
1366 entity pl_left = players.sort_next;
1367 entity pl_right = pl_left.sort_next;
1369 Scoreboard_Duel_DrawTable(pos, true, pl_left, tm);
1370 Scoreboard_Duel_DrawTable(pos + eX * screen_half + eX * weapon_margin, false, pl_right, tm);
1372 end_pos.y += panel_size.y + (panel_bg_padding * 2);
1373 panel_size.x = screen_half * 2;
1377 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1379 int max_players = 999;
1380 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1382 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1385 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1386 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1387 height /= team_count;
1390 height -= panel_bg_padding * 2; // - padding
1391 max_players = floor(height / (hud_fontsize.y * 1.25));
1392 if(max_players <= 1)
1394 if(max_players == tm.team_size)
1399 entity me = playerslots[current_player];
1401 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1402 panel_size.y += panel_bg_padding * 2;
1405 vector end_pos = panel_pos + eY * (panel_size.y + 0.5* hud_fontsize.y);
1406 if(panel.current_panel_bg != "0")
1407 end_pos.y += panel_bg_border * 2;
1409 if(panel_bg_padding)
1411 panel_pos += '1 1 0' * panel_bg_padding;
1412 panel_size -= '2 2 0' * panel_bg_padding;
1416 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1420 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1422 pos.y += 1.25 * hud_fontsize.y;
1425 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1427 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1430 // print header row and highlight columns
1431 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size), tm.team);
1433 // fill the table and draw the rows
1434 bool is_self = false;
1435 bool self_shown = false;
1438 if(Team_IsValidTeam(tm.team)) average_ping[Team_TeamToIndex(tm.team) - 1] = 0;
1439 for(pl = players.sort_next; pl; pl = pl.sort_next)
1441 if(pl.team != tm.team)
1443 if(i == max_players - 2 && pl != me)
1445 if(!self_shown && me.team == tm.team)
1447 Scoreboard_DrawItem(pos, rgb, me, true, i);
1449 pos.y += 1.25 * hud_fontsize.y;
1453 if(i >= max_players - 1)
1455 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1458 is_self = (pl.sv_entnum == current_player);
1459 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1461 if(Team_IsValidTeam(tm.team) && pl.ping) {
1462 average_ping[Team_TeamToIndex(tm.team) - 1] += pl.ping;
1467 pos.y += 1.25 * hud_fontsize.y;
1470 if(with_ping) average_ping[Team_TeamToIndex(tm.team) - 1] /= with_ping;
1472 panel_size.x += panel_bg_padding * 2; // restore initial width
1476 bool Scoreboard_WouldDraw()
1478 if (MUTATOR_CALLHOOK(DrawScoreboard))
1480 else if (QuickMenu_IsOpened())
1482 else if (HUD_Radar_Clickable())
1484 else if (scoreboard_showscores)
1486 else if (intermission == 1)
1488 else if (intermission == 2)
1490 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1491 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1495 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1500 vector Scoreboard_MedalStats_Draw(vector pos)
1503 float height = hud_fontsize.y * 2;
1505 entity pl = playerslots[current_player];
1507 vector title_pos = pos;
1508 pos.x += 0.5 * hud_fontsize.x + panel_bg_padding;
1509 pos.y += 1.25 * hud_fontsize.y;
1513 pos = Scoreboard_DrawMedal(pos, "gfx/medal/airshot", height, pl.(scores(SP_MEDAL_AIRSHOT)));
1514 pos = Scoreboard_DrawMedal(pos, "gfx/medal/damage", height, pl.(scores(SP_MEDAL_DAMAGE)));
1515 pos = Scoreboard_DrawMedal(pos, "gfx/medal/electrobitch", height, pl.(scores(SP_MEDAL_ELECTROBITCH)));
1516 pos = Scoreboard_DrawMedal(pos, "gfx/medal/excellent", height, pl.(scores(SP_MEDAL_EXCELLENT)));
1517 pos = Scoreboard_DrawMedal(pos, "gfx/medal/firstblood", height, pl.(scores(SP_MEDAL_FIRSTBLOOD)));
1518 pos = Scoreboard_DrawMedal(pos, "gfx/medal/headshot", height, pl.(scores(SP_MEDAL_HEADSHOT)));
1519 pos = Scoreboard_DrawMedal(pos, "gfx/medal/humiliation", height, pl.(scores(SP_MEDAL_HUMILIATION)));
1520 pos = Scoreboard_DrawMedal(pos, "gfx/medal/impressive", height, pl.(scores(SP_MEDAL_IMPRESSIVE)));
1521 pos = Scoreboard_DrawMedal(pos, "gfx/medal/yoda", height, pl.(scores(SP_MEDAL_YODA)));
1523 pos.x += hud_fontsize.x;
1525 pos = Scoreboard_DrawMedal(pos, "gfx/medal/accuracy", height, pl.(scores(SP_MEDAL_ACCURACY)));
1526 pos = Scoreboard_DrawMedal(pos, "gfx/medal/assist", height, pl.(scores(SP_MEDAL_ASSIST)));
1527 pos = Scoreboard_DrawMedal(pos, "gfx/medal/defense", height, pl.(scores(SP_MEDAL_DEFENSE)));
1528 pos = Scoreboard_DrawMedal(pos, "gfx/medal/perfect", height, pl.(scores(SP_MEDAL_PERFECT)));
1530 if(!total_medals) return orig;
1532 drawstring(title_pos, sprintf(_("Medal stats (total %d)"), total_medals),
1533 hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1536 pos.y += height + hud_fontsize.y * 0.5;
1540 float average_accuracy;
1541 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1545 if (scoreboard_fade_alpha == 1)
1546 scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
1548 scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
1550 vector initial_pos = pos;
1552 WepSet weapons_stat = WepSet_GetFromStat();
1553 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1554 int disownedcnt = 0;
1556 FOREACH(Weapons, it != WEP_Null, {
1557 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1559 WepSet set = it.m_wepset;
1560 if(it.spawnflags & WEP_TYPE_OTHER)
1565 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1567 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1574 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1575 if (weapon_cnt <= 0) return pos;
1578 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1580 int columnns = ceil(weapon_cnt / rows);
1582 float weapon_height = 29;
1583 float height = hud_fontsize.y + weapon_height;
1585 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);
1586 pos.y += 1.25 * hud_fontsize.y;
1587 if(panel.current_panel_bg != "0")
1588 pos.y += panel_bg_border;
1591 panel_size.y = height * rows;
1592 panel_size.y += panel_bg_padding * 2;
1594 float panel_bg_alpha_save = panel_bg_alpha;
1595 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1597 panel_bg_alpha = panel_bg_alpha_save;
1599 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1600 if(panel.current_panel_bg != "0")
1601 end_pos.y += panel_bg_border * 2;
1603 if(panel_bg_padding)
1605 panel_pos += '1 1 0' * panel_bg_padding;
1606 panel_size -= '2 2 0' * panel_bg_padding;
1610 vector tmp = panel_size;
1612 float weapon_width = tmp.x / columnns / rows;
1615 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1619 // column highlighting
1620 for (int i = 0; i < columnns; ++i)
1622 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);
1625 for (int i = 0; i < rows; ++i)
1626 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1629 average_accuracy = 0;
1630 int weapons_with_stats = 0;
1632 pos.x += weapon_width / 2;
1634 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1637 Accuracy_LoadColors();
1639 float oldposx = pos.x;
1643 FOREACH(Weapons, it != WEP_Null, {
1644 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1646 WepSet set = it.m_wepset;
1647 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1649 if (it.spawnflags & WEP_TYPE_OTHER)
1653 if (weapon_stats >= 0)
1654 weapon_alpha = sbt_fg_alpha;
1656 weapon_alpha = 0.2 * sbt_fg_alpha;
1659 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1661 if (weapon_stats >= 0) {
1662 weapons_with_stats += 1;
1663 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1666 s = sprintf("%d%%", weapon_stats * 100);
1669 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1671 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1672 rgb = Accuracy_GetColor(weapon_stats);
1674 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1676 tmpos.x += weapon_width * rows;
1677 pos.x += weapon_width * rows;
1678 if (rows == 2 && column == columnns - 1) {
1686 if (weapons_with_stats)
1687 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1689 panel_size.x += panel_bg_padding * 2; // restore initial width
1691 if (scoreboard_acc_fade_alpha == 1)
1693 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1696 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1698 pos.x += hud_fontsize.x * 0.25;
1699 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1700 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1701 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1703 pos.y += hud_fontsize.y;
1709 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1710 float stat_secrets_found, stat_secrets_total;
1711 float stat_monsters_killed, stat_monsters_total;
1715 // get monster stats
1716 stat_monsters_killed = STAT(MONSTERS_KILLED);
1717 stat_monsters_total = STAT(MONSTERS_TOTAL);
1719 // get secrets stats
1720 stat_secrets_found = STAT(SECRETS_FOUND);
1721 stat_secrets_total = STAT(SECRETS_TOTAL);
1723 // get number of rows
1724 if(stat_secrets_total)
1726 if(stat_monsters_total)
1729 // if no rows, return
1733 // draw table header
1734 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1735 pos.y += 1.25 * hud_fontsize.y;
1736 if(panel.current_panel_bg != "0")
1737 pos.y += panel_bg_border;
1740 panel_size.y = hud_fontsize.y * rows;
1741 panel_size.y += panel_bg_padding * 2;
1744 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1745 if(panel.current_panel_bg != "0")
1746 end_pos.y += panel_bg_border * 2;
1748 if(panel_bg_padding)
1750 panel_pos += '1 1 0' * panel_bg_padding;
1751 panel_size -= '2 2 0' * panel_bg_padding;
1755 vector tmp = panel_size;
1758 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1761 if(stat_monsters_total)
1763 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1764 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1768 if(stat_secrets_total)
1770 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1771 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1774 panel_size.x += panel_bg_padding * 2; // restore initial width
1779 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1782 RANKINGS_RECEIVED_CNT = 0;
1783 for (i=RANKINGS_CNT-1; i>=0; --i)
1785 ++RANKINGS_RECEIVED_CNT;
1787 if (RANKINGS_RECEIVED_CNT == 0)
1790 vector hl_rgb = rgb + '0.5 0.5 0.5';
1792 pos.y += hud_fontsize.y;
1793 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1794 pos.y += 1.25 * hud_fontsize.y;
1795 if(panel.current_panel_bg != "0")
1796 pos.y += panel_bg_border;
1801 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1803 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1808 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1810 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1814 float ranksize = 3 * hud_fontsize.x;
1815 float timesize = 5 * hud_fontsize.x;
1816 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1817 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1818 columns = min(columns, RANKINGS_RECEIVED_CNT);
1820 // expand name column to fill the entire row
1821 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1822 namesize += available_space;
1823 columnsize.x += available_space;
1825 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1826 panel_size.y += panel_bg_padding * 2;
1830 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1831 if(panel.current_panel_bg != "0")
1832 end_pos.y += panel_bg_border * 2;
1834 if(panel_bg_padding)
1836 panel_pos += '1 1 0' * panel_bg_padding;
1837 panel_size -= '2 2 0' * panel_bg_padding;
1843 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1845 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1847 int column = 0, j = 0;
1848 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1849 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1856 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1857 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1858 else if(!((j + column) & 1) && sbt_highlight)
1859 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1861 str = count_ordinal(i+1);
1862 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1863 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1864 str = ColorTranslateRGB(grecordholder[i]);
1866 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1867 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1869 pos.y += 1.25 * hud_fontsize.y;
1871 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1875 pos.x += panel_size.x / columns;
1876 pos.y = panel_pos.y;
1879 strfree(zoned_name_self);
1881 panel_size.x += panel_bg_padding * 2; // restore initial width
1885 float scoreboard_time;
1886 bool have_weapon_stats;
1887 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1889 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1891 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1894 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1895 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1901 if (!have_weapon_stats)
1903 FOREACH(Weapons, it != WEP_Null, {
1904 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1905 if (weapon_stats >= 0)
1907 have_weapon_stats = true;
1911 if (!have_weapon_stats)
1918 void Scoreboard_Draw()
1920 if(!autocvar__hud_configure)
1922 if(!hud_draw_maximized) return;
1924 // frametime checks allow to toggle the scoreboard even when the game is paused
1925 if(scoreboard_active) {
1926 if (scoreboard_fade_alpha < 1)
1927 scoreboard_time = time;
1928 if(hud_configure_menu_open == 1)
1929 scoreboard_fade_alpha = 1;
1930 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1931 if (scoreboard_fadeinspeed && frametime)
1932 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1934 scoreboard_fade_alpha = 1;
1935 if(hud_fontsize_str != autocvar_hud_fontsize)
1937 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1938 Scoreboard_initFieldSizes();
1939 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1943 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1944 if (scoreboard_fadeoutspeed && frametime)
1945 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1947 scoreboard_fade_alpha = 0;
1950 if (!scoreboard_fade_alpha)
1952 scoreboard_acc_fade_alpha = 0;
1957 scoreboard_fade_alpha = 0;
1959 if (autocvar_hud_panel_scoreboard_dynamichud)
1962 HUD_Scale_Disable();
1964 if(scoreboard_fade_alpha <= 0)
1966 panel_fade_alpha *= scoreboard_fade_alpha;
1967 HUD_Panel_LoadCvars();
1969 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1970 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1971 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1972 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1973 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1974 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1976 // don't overlap with con_notify
1977 if(!autocvar__hud_configure)
1978 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1980 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1981 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1982 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1983 panel_size.x = fixed_scoreboard_width;
1985 Scoreboard_UpdatePlayerTeams();
1987 vector pos = panel_pos;
1992 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1994 // Begin of Game Info Section
1995 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1996 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1999 //drawcolorcodedstring(pos, "bienvenidoainternet.org", sb_gameinfo_type_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2000 //drawpic_aspect(pos + '1 0 0' * (panel_size.x - 150), "gfx/bai_logo", vec2(150, sb_gameinfo_type_fontsize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2001 //pos.y += sb_gameinfo_type_fontsize.y;
2003 // Game Info: Game Type
2004 str = MapInfo_Type_ToText(gametype);
2006 draw_beginBoldFont();
2007 //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);
2008 drawcolorcodedstring(pos, str, sb_gameinfo_type_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2011 vector tmp_old_sz = draw_getimagesize("gfx/bai_logo");
2012 float tmp_aspect = tmp_old_sz.x/tmp_old_sz.y;
2013 vector tmp_new_sz = vec2(sb_gameinfo_type_fontsize.y * tmp_aspect, sb_gameinfo_type_fontsize.y);
2015 drawpic(pos + '1 0 0' * (panel_size.x - tmp_new_sz.x), "gfx/bai_logo", tmp_new_sz, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2017 pos.y += sb_gameinfo_type_fontsize.y;
2020 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth_colors(hostname_full, sb_gameinfo_detail_fontsize)), hostname_full, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2022 pos.y += sb_gameinfo_detail_fontsize.y;
2024 // Game Info: Game Detail
2025 float tl = STAT(TIMELIMIT);
2026 float fl = STAT(FRAGLIMIT);
2027 float ll = STAT(LEADLIMIT);
2028 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2031 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2032 if(!gametype.m_hidelimits)
2037 str = strcat(str, "^7 / "); // delimiter
2040 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
2041 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2042 (teamscores_label(ts_primary) == "fastest") ? "" :
2043 TranslateScoresLabel(teamscores_label(ts_primary))));
2047 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
2048 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2049 (scores_label(ps_primary) == "fastest") ? "" :
2050 TranslateScoresLabel(scores_label(ps_primary))));
2055 if(tl > 0 || fl > 0)
2058 if (ll_and_fl && fl > 0)
2059 str = strcat(str, "^7 & ");
2061 str = strcat(str, "^7 / ");
2066 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
2067 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2068 (teamscores_label(ts_primary) == "fastest") ? "" :
2069 TranslateScoresLabel(teamscores_label(ts_primary))));
2073 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
2074 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2075 (scores_label(ps_primary) == "fastest") ? "" :
2076 TranslateScoresLabel(scores_label(ps_primary))));
2081 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
2083 str = sprintf(_("^7Map: ^2%s"), shortmapname);
2084 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2085 // End of Game Info Section
2087 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2088 if(panel.current_panel_bg != "0")
2089 pos.y += panel_bg_border;
2091 // Draw the scoreboard
2092 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2095 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2099 vector panel_bg_color_save = panel_bg_color;
2100 vector team_score_baseoffset;
2101 vector team_size_baseoffset;
2102 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2104 // put team score to the left of scoreboard (and team size to the right)
2105 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 1.5;
2106 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2107 if(panel.current_panel_bg != "0")
2109 team_score_baseoffset.x -= panel_bg_border;
2110 team_size_baseoffset.x += panel_bg_border;
2115 // put team score to the right of scoreboard (and team size to the left)
2116 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 1.5;
2117 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2118 if(panel.current_panel_bg != "0")
2120 team_score_baseoffset.x += panel_bg_border;
2121 team_size_baseoffset.x -= panel_bg_border;
2125 int team_size_total = 0;
2126 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2128 // calculate team size total (sum of all team sizes)
2129 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2130 if(tm.team != NUM_SPECTATOR)
2131 team_size_total += tm.team_size;
2134 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2136 if(tm.team == NUM_SPECTATOR)
2141 vector rgb = Team_ColorRGB(tm.team);
2142 /*draw_beginBoldFont();
2143 str = ftos(tm.(teamscores(ts_primary)));
2144 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2146 // team score on the left (default)
2147 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 3);
2151 // team score on the right
2152 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 3);
2154 drawstring(str_pos, str, hud_fontsize * 3, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2156 // team size (if set to show on the side)
2157 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2159 // calculate the starting position for the whole team size info string
2160 str = sprintf("%d/%d", tm.team_size, team_size_total);
2161 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2163 // team size on the left
2164 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2168 // team size on the right
2169 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2171 str = sprintf("%d", tm.team_size);
2172 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2173 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2174 str = sprintf("/%d", team_size_total);
2175 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2179 // secondary score, e.g. keyhunt
2180 if(ts_primary != ts_secondary)
2182 str = ftos(tm.(teamscores(ts_secondary)));
2183 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2186 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2191 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2194 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2199 // z411 My team header
2201 drawfill(pos, team_score_size, rgb * 0.5, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2204 str = ftos(tm.(teamscores(ts_primary)));
2206 str_pos.x += (team_score_size.x / 2) - (stringwidth(str, true, team_score_fontsize) / 2);
2207 str_pos.y += (team_score_size.y / 2) - (team_score_fontsize.y / 2);
2209 draw_beginBoldFont();
2210 drawstring(str_pos, str, team_score_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2214 str = Team_CustomName(tm.team);
2216 str_pos.x += team_score_size.x + team_name_fontsize.x * 0.5;
2217 str_pos.y += (team_score_size.y / 2) - (team_name_fontsize.y / 2);
2218 drawcolorcodedstring(str_pos, str, team_name_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2220 pos.y += team_score_size.y + (hud_fontsize.y * 0.5);
2222 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2223 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2224 else if(panel_bg_color_team > 0)
2225 panel_bg_color = rgb * panel_bg_color_team;
2227 panel_bg_color = rgb;
2228 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2230 panel_bg_color = panel_bg_color_save;
2232 else if(gametype == MAPINFO_TYPE_DUEL)
2234 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2235 if(tm.team != NUM_SPECTATOR)
2238 // z411 make DUEL TABLE
2239 pos = Scoreboard_MakeDuelTable(pos, tm, panel_bg_color, bg_size);
2243 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2244 if(tm.team != NUM_SPECTATOR)
2247 // display it anyway
2248 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2251 pos = Scoreboard_MedalStats_Draw(pos);
2253 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2254 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2256 if(MUTATOR_CALLHOOK(ShowRankings)) {
2257 string ranktitle = M_ARGV(0, string);
2258 if(race_speedaward) {
2259 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);
2260 pos.y += 1.25 * hud_fontsize.y;
2262 if(race_speedaward_alltimebest) {
2263 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);
2264 pos.y += 1.25 * hud_fontsize.y;
2266 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2269 //pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2272 for(pl = players.sort_next; pl; pl = pl.sort_next)
2274 if(pl.team == NUM_SPECTATOR)
2276 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2277 if(tm.team == NUM_SPECTATOR)
2279 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2280 draw_beginBoldFont();
2281 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2283 pos.y += 1.25 * hud_fontsize.y;
2285 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2286 pos.y += 1.25 * hud_fontsize.y;
2293 // print information about respawn status
2294 float respawn_time = STAT(RESPAWN_TIME);
2298 if(respawn_time < 0)
2300 // a negative number means we are awaiting respawn, time value is still the same
2301 respawn_time *= -1; // remove mark now that we checked it
2303 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2304 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2306 str = sprintf(_("^1Respawning in ^3%s^1..."),
2307 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2308 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2310 count_seconds(ceil(respawn_time - time))
2314 else if(time < respawn_time)
2316 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2317 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2318 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2320 count_seconds(ceil(respawn_time - time))
2324 else if(time >= respawn_time)
2325 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2327 pos.y += 1.2 * hud_fontsize.y;
2328 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2331 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;