1 #include "scoreboard.qh"
3 #include <client/autocvars.qh>
4 #include <client/defs.qh>
5 #include <client/main.qh>
6 #include <client/miscfunctions.qh>
7 #include "quickmenu.qh"
8 #include <common/ent_cs.qh>
9 #include <common/constants.qh>
10 #include <common/net_linked.qh>
11 #include <common/mapinfo.qh>
12 #include <common/minigames/cl_minigames.qh>
13 #include <common/scores.qh>
14 #include <common/stats.qh>
15 #include <common/teams.qh>
19 const int MAX_SBT_FIELDS = MAX_SCORE;
21 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
22 float sbt_field_size[MAX_SBT_FIELDS + 1];
23 string sbt_field_title[MAX_SBT_FIELDS + 1];
26 string autocvar_hud_fontsize;
27 string hud_fontsize_str;
32 float sbt_fg_alpha_self;
34 float sbt_highlight_alpha;
35 float sbt_highlight_alpha_self;
37 // provide basic panel cvars to old clients
38 // TODO remove them after a future release (0.8.2+)
39 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
40 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
41 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
42 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
43 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
44 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
45 noref string autocvar_hud_panel_scoreboard_bg_border = "";
46 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
48 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
49 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
50 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
51 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
52 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
53 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
54 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
55 bool autocvar_hud_panel_scoreboard_table_highlight = true;
56 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
57 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
58 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
59 float autocvar_hud_panel_scoreboard_namesize = 15;
60 float autocvar_hud_panel_scoreboard_team_size_position = 0;
62 bool autocvar_hud_panel_scoreboard_accuracy = true;
63 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
64 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
65 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
66 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
68 bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
70 bool autocvar_hud_panel_scoreboard_dynamichud = false;
72 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
73 bool autocvar_hud_panel_scoreboard_others_showscore = true;
74 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
75 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
76 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
78 // mode 0: returns translated label
79 // mode 1: prints name and description of all the labels
80 string Label_getInfo(string label, int mode)
83 label = "bckills"; // first case in the switch
87 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_INFO(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
88 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_INFO(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
89 case "caps": if (!mode) return CTX(_("SCO^caps")); else LOG_INFO(strcat("^3", "caps", " ^7", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
90 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_INFO(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
91 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_INFO(strcat("^3", "deaths", " ^7", _("Number of deaths")));
92 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_INFO(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
93 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_INFO(strcat("^3", "dmg", " ^7", _("The total damage done")));
94 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_INFO(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
95 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_INFO(strcat("^3", "drops", " ^7", _("Number of flag drops")));
96 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_INFO(strcat("^3", "elo", " ^7", _("Player ELO")));
97 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_INFO(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
98 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_INFO(strcat("^3", "faults", " ^7", _("Number of faults committed")));
99 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_INFO(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
100 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_INFO(strcat("^3", "fps", " ^7", _("FPS")));
101 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_INFO(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
102 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_INFO(strcat("^3", "goals", " ^7", _("Number of goals scored")));
103 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_INFO(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
104 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_INFO(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
105 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_INFO(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
106 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_INFO(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
107 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_INFO(strcat("^3", "kills", " ^7", _("Number of kills")));
108 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_INFO(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
109 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_INFO(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
110 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_INFO(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
111 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_INFO(strcat("^3", "name", " ^7", _("Player name")));
112 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_INFO(strcat("^3", "nick", " ^7", _("Player name")));
113 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_INFO(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
114 case "pickups": if (!mode) return CTX(_("SCO^pickups")); else LOG_INFO(strcat("^3", "pickups", " ^7", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
115 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_INFO(strcat("^3", "ping", " ^7", _("Ping time")));
116 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_INFO(strcat("^3", "pl", " ^7", _("Packet loss")));
117 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_INFO(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
118 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_INFO(strcat("^3", "rank", " ^7", _("Player rank")));
119 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_INFO(strcat("^3", "returns", " ^7", _("Number of flag returns")));
120 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_INFO(strcat("^3", "revivals", " ^7", _("Number of revivals")));
121 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_INFO(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
122 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_INFO(strcat("^3", "score", " ^7", _("Total score")));
123 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_INFO(strcat("^3", "suicides", " ^7", _("Number of suicides")));
124 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_INFO(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
125 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_INFO(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
126 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_INFO(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
127 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_INFO(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
128 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_INFO(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
129 default: return label;
134 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
135 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
137 void Scoreboard_InitScores()
141 ps_primary = ps_secondary = NULL;
142 ts_primary = ts_secondary = -1;
143 FOREACH(Scores, true, {
144 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
145 if(f == SFL_SORT_PRIO_PRIMARY)
147 if(f == SFL_SORT_PRIO_SECONDARY)
150 if(ps_secondary == NULL)
151 ps_secondary = ps_primary;
153 for(i = 0; i < MAX_TEAMSCORE; ++i)
155 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
156 if(f == SFL_SORT_PRIO_PRIMARY)
158 if(f == SFL_SORT_PRIO_SECONDARY)
161 if(ts_secondary == -1)
162 ts_secondary = ts_primary;
164 Cmd_Scoreboard_SetFields(0);
168 void Scoreboard_UpdatePlayerTeams()
172 for(pl = players.sort_next; pl; pl = pl.sort_next)
175 int Team = entcs_GetScoreTeam(pl.sv_entnum);
176 if(SetTeam(pl, Team))
179 Scoreboard_UpdatePlayerPos(pl);
183 pl = players.sort_next;
188 print(strcat("PNUM: ", ftos(num), "\n"));
193 int Scoreboard_CompareScore(int vl, int vr, int f)
195 TC(int, vl); TC(int, vr); TC(int, f);
196 if(f & SFL_ZERO_IS_WORST)
198 if(vl == 0 && vr != 0)
200 if(vl != 0 && vr == 0)
204 return IS_INCREASING(f);
206 return IS_DECREASING(f);
210 float Scoreboard_ComparePlayerScores(entity left, entity right)
213 vl = entcs_GetTeam(left.sv_entnum);
214 vr = entcs_GetTeam(right.sv_entnum);
226 if(vl == NUM_SPECTATOR)
228 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
230 if(!left.gotscores && right.gotscores)
235 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
239 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
243 FOREACH(Scores, true, {
244 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
245 if (r >= 0) return r;
248 if (left.sv_entnum < right.sv_entnum)
254 void Scoreboard_UpdatePlayerPos(entity player)
257 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
259 SORT_SWAP(player, ent);
261 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
263 SORT_SWAP(ent, player);
267 float Scoreboard_CompareTeamScores(entity left, entity right)
271 if(left.team == NUM_SPECTATOR)
273 if(right.team == NUM_SPECTATOR)
276 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
280 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
284 for(i = 0; i < MAX_TEAMSCORE; ++i)
286 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
291 if (left.team < right.team)
297 void Scoreboard_UpdateTeamPos(entity Team)
300 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
302 SORT_SWAP(Team, ent);
304 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
306 SORT_SWAP(ent, Team);
310 void Cmd_Scoreboard_Help()
312 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
313 LOG_INFO(_("Usage:"));
314 LOG_INFO("^2scoreboard_columns_set ^3default");
315 LOG_INFO(_("^2scoreboard_columns_set ^3field1 field2 ..."));
316 LOG_INFO(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
317 LOG_INFO(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
318 LOG_INFO(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
319 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields."));
320 LOG_INFO(_("The following field names are recognized (case insensitive):"));
326 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
327 "of game types, then a slash, to make the field show up only in these\n"
328 "or in all but these game types. You can also specify 'all' as a\n"
329 "field to show all fields available for the current game mode."));
332 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
333 "include/exclude ALL teams/noteams game modes."));
336 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
337 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
338 "right of the vertical bar aligned to the right."));
339 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
340 "other gamemodes except DM."));
343 // NOTE: adding a gametype with ? to not warn for an optional field
344 // make sure it's excluded in a previous exclusive rule, if any
345 // otherwise the previous exclusive rule warns anyway
346 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
347 #define SCOREBOARD_DEFAULT_COLUMNS \
348 "ping pl fps name |" \
349 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
350 " -teams,lms/deaths +ft,tdm/deaths" \
352 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
353 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
354 " +tdm,ft,dom,ons,as/teamkills"\
355 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
356 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
357 " +lms/lives +lms/rank" \
358 " +kh/kckills +kh/losses +kh/caps" \
359 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
360 " +as/objectives +nb/faults +nb/goals" \
361 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
362 " +dom/ticks +dom/takes" \
363 " -lms,rc,cts,inv,nb/score"
365 void Cmd_Scoreboard_SetFields(int argc)
370 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
374 return; // do nothing, we don't know gametype and scores yet
376 // sbt_fields uses strunzone on the titles!
377 if(!sbt_field_title[0])
378 for(i = 0; i < MAX_SBT_FIELDS; ++i)
379 sbt_field_title[i] = strzone("(null)");
381 // TODO: re enable with gametype dependant cvars?
382 if(argc < 3) // no arguments provided
383 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
386 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
390 if(argv(2) == "default" || argv(2) == "expand_default")
392 if(argv(2) == "expand_default")
393 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
394 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
396 else if(argv(2) == "all")
398 string s = "ping pl name |"; // scores without a label
399 FOREACH(Scores, true, {
401 if(it != ps_secondary)
402 if(scores_label(it) != "")
403 s = strcat(s, " ", scores_label(it));
405 if(ps_secondary != ps_primary)
406 s = strcat(s, " ", scores_label(ps_secondary));
407 s = strcat(s, " ", scores_label(ps_primary));
408 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
415 hud_fontsize = HUD_GetFontsize("hud_fontsize");
417 for(i = 1; i < argc - 1; ++i)
420 bool nocomplain = false;
421 if(substring(str, 0, 1) == "?")
424 str = substring(str, 1, strlen(str) - 1);
427 slash = strstrofs(str, "/", 0);
430 pattern = substring(str, 0, slash);
431 str = substring(str, slash + 1, strlen(str) - (slash + 1));
433 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
437 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
438 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
439 str = strtolower(str);
444 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
445 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
446 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
447 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
448 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
449 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
450 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
451 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
452 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
453 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
456 FOREACH(Scores, true, {
457 if (str == strtolower(scores_label(it))) {
459 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
469 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
473 sbt_field[sbt_num_fields] = j;
476 if(j == ps_secondary)
477 have_secondary = true;
482 if(sbt_num_fields >= MAX_SBT_FIELDS)
486 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
488 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
489 have_secondary = true;
490 if(ps_primary == ps_secondary)
491 have_secondary = true;
492 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
494 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
498 strunzone(sbt_field_title[sbt_num_fields]);
499 for(i = sbt_num_fields; i > 0; --i)
501 sbt_field_title[i] = sbt_field_title[i-1];
502 sbt_field_size[i] = sbt_field_size[i-1];
503 sbt_field[i] = sbt_field[i-1];
505 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
506 sbt_field[0] = SP_NAME;
508 LOG_INFO("fixed missing field 'name'");
512 strunzone(sbt_field_title[sbt_num_fields]);
513 for(i = sbt_num_fields; i > 1; --i)
515 sbt_field_title[i] = sbt_field_title[i-1];
516 sbt_field_size[i] = sbt_field_size[i-1];
517 sbt_field[i] = sbt_field[i-1];
519 sbt_field_title[1] = strzone("|");
520 sbt_field[1] = SP_SEPARATOR;
521 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
523 LOG_INFO("fixed missing field '|'");
526 else if(!have_separator)
528 strcpy(sbt_field_title[sbt_num_fields], "|");
529 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
530 sbt_field[sbt_num_fields] = SP_SEPARATOR;
532 LOG_INFO("fixed missing field '|'");
536 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
537 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
538 sbt_field[sbt_num_fields] = ps_secondary;
540 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
544 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
545 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
546 sbt_field[sbt_num_fields] = ps_primary;
548 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
552 sbt_field[sbt_num_fields] = SP_END;
556 vector sbt_field_rgb;
557 string sbt_field_icon0;
558 string sbt_field_icon1;
559 string sbt_field_icon2;
560 vector sbt_field_icon0_rgb;
561 vector sbt_field_icon1_rgb;
562 vector sbt_field_icon2_rgb;
563 string Scoreboard_GetName(entity pl)
565 if(ready_waiting && pl.ready)
567 sbt_field_icon0 = "gfx/scoreboard/player_ready";
571 int f = entcs_GetClientColors(pl.sv_entnum);
573 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
574 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
575 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
576 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
577 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
580 return entcs_GetName(pl.sv_entnum);
583 string Scoreboard_GetField(entity pl, PlayerScoreField field)
585 float tmp, num, denom;
588 sbt_field_rgb = '1 1 1';
589 sbt_field_icon0 = "";
590 sbt_field_icon1 = "";
591 sbt_field_icon2 = "";
592 sbt_field_icon0_rgb = '1 1 1';
593 sbt_field_icon1_rgb = '1 1 1';
594 sbt_field_icon2_rgb = '1 1 1';
599 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
600 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
604 tmp = max(0, min(220, f-80)) / 220;
605 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
611 f = pl.ping_packetloss;
612 tmp = pl.ping_movementloss;
613 if(f == 0 && tmp == 0)
615 str = ftos(ceil(f * 100));
617 str = strcat(str, "~", ftos(ceil(tmp * 100)));
618 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
619 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
623 return Scoreboard_GetName(pl);
626 f = pl.(scores(SP_KILLS));
627 f -= pl.(scores(SP_SUICIDES));
631 num = pl.(scores(SP_KILLS));
632 denom = pl.(scores(SP_DEATHS));
635 sbt_field_rgb = '0 1 0';
636 str = sprintf("%d", num);
637 } else if(num <= 0) {
638 sbt_field_rgb = '1 0 0';
639 str = sprintf("%.1f", num/denom);
641 str = sprintf("%.1f", num/denom);
645 f = pl.(scores(SP_KILLS));
646 f -= pl.(scores(SP_DEATHS));
649 sbt_field_rgb = '0 1 0';
651 sbt_field_rgb = '1 1 1';
653 sbt_field_rgb = '1 0 0';
659 float elo = pl.(scores(SP_ELO));
661 case -1: return "...";
662 case -2: return _("N/A");
663 default: return ftos(elo);
669 float fps = pl.(scores(SP_FPS));
672 sbt_field_rgb = '1 1 1';
673 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
675 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200);
676 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
680 case SP_DMG: case SP_DMGTAKEN:
681 return sprintf("%.1f k", pl.(scores(field)) / 1000);
683 default: case SP_SCORE:
684 tmp = pl.(scores(field));
685 f = scores_flags(field);
686 if(field == ps_primary)
687 sbt_field_rgb = '1 1 0';
688 else if(field == ps_secondary)
689 sbt_field_rgb = '0 1 1';
691 sbt_field_rgb = '1 1 1';
692 return ScoreString(f, tmp);
697 float sbt_fixcolumnwidth_len;
698 float sbt_fixcolumnwidth_iconlen;
699 float sbt_fixcolumnwidth_marginlen;
701 string Scoreboard_FixColumnWidth(int i, string str)
707 sbt_fixcolumnwidth_iconlen = 0;
709 if(sbt_field_icon0 != "")
711 sz = draw_getimagesize(sbt_field_icon0);
713 if(sbt_fixcolumnwidth_iconlen < f)
714 sbt_fixcolumnwidth_iconlen = f;
717 if(sbt_field_icon1 != "")
719 sz = draw_getimagesize(sbt_field_icon1);
721 if(sbt_fixcolumnwidth_iconlen < f)
722 sbt_fixcolumnwidth_iconlen = f;
725 if(sbt_field_icon2 != "")
727 sz = draw_getimagesize(sbt_field_icon2);
729 if(sbt_fixcolumnwidth_iconlen < f)
730 sbt_fixcolumnwidth_iconlen = f;
733 if(sbt_fixcolumnwidth_iconlen != 0)
735 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
736 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
739 sbt_fixcolumnwidth_marginlen = 0;
741 if(sbt_field[i] == SP_NAME) // name gets all remaining space
744 float remaining_space = 0;
745 for(j = 0; j < sbt_num_fields; ++j)
747 if (sbt_field[i] != SP_SEPARATOR)
748 remaining_space += sbt_field_size[j] + hud_fontsize.x;
749 sbt_field_size[i] = panel_size.x - remaining_space;
751 if (sbt_fixcolumnwidth_iconlen != 0)
752 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
753 float namesize = panel_size.x - remaining_space;
754 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
755 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
757 max_namesize = vid_conwidth - remaining_space;
760 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
762 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
763 if(sbt_field_size[i] < f)
764 sbt_field_size[i] = f;
769 void Scoreboard_initFieldSizes()
771 for(int i = 0; i < sbt_num_fields; ++i)
773 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
774 Scoreboard_FixColumnWidth(i, "");
778 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
781 vector column_dim = eY * panel_size.y;
783 column_dim.y -= 1.25 * hud_fontsize.y;
784 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
785 pos.x += hud_fontsize.x * 0.5;
786 for(i = 0; i < sbt_num_fields; ++i)
788 if(sbt_field[i] == SP_SEPARATOR)
790 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
793 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
794 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
795 pos.x += column_dim.x;
797 if(sbt_field[i] == SP_SEPARATOR)
799 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
800 for(i = sbt_num_fields - 1; i > 0; --i)
802 if(sbt_field[i] == SP_SEPARATOR)
805 pos.x -= sbt_field_size[i];
810 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
811 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
814 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
815 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
816 pos.x -= hud_fontsize.x;
821 pos.y += 1.25 * hud_fontsize.y;
825 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
827 TC(bool, is_self); TC(int, pl_number);
829 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
831 vector h_pos = item_pos;
832 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
833 // alternated rows highlighting
835 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
836 else if((sbt_highlight) && (!(pl_number % 2)))
837 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
839 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
841 vector pos = item_pos;
842 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
844 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);
846 pos.x += hud_fontsize.x * 0.5;
847 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
848 vector tmp = '0 0 0';
850 PlayerScoreField field;
851 for(i = 0; i < sbt_num_fields; ++i)
853 field = sbt_field[i];
854 if(field == SP_SEPARATOR)
857 if(is_spec && field != SP_NAME && field != SP_PING) {
858 pos.x += sbt_field_size[i] + hud_fontsize.x;
861 str = Scoreboard_GetField(pl, field);
862 str = Scoreboard_FixColumnWidth(i, str);
864 pos.x += sbt_field_size[i] + hud_fontsize.x;
866 if(field == SP_NAME) {
867 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
868 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
870 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
871 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
874 tmp.x = sbt_field_size[i] + hud_fontsize.x;
875 if(sbt_field_icon0 != "")
876 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
877 if(sbt_field_icon1 != "")
878 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
879 if(sbt_field_icon2 != "")
880 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
883 if(sbt_field[i] == SP_SEPARATOR)
885 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
886 for(i = sbt_num_fields-1; i > 0; --i)
888 field = sbt_field[i];
889 if(field == SP_SEPARATOR)
892 if(is_spec && field != SP_NAME && field != SP_PING) {
893 pos.x -= sbt_field_size[i] + hud_fontsize.x;
897 str = Scoreboard_GetField(pl, field);
898 str = Scoreboard_FixColumnWidth(i, str);
900 if(field == SP_NAME) {
901 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
902 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
904 tmp.x = sbt_fixcolumnwidth_len;
905 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
908 tmp.x = sbt_field_size[i];
909 if(sbt_field_icon0 != "")
910 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
911 if(sbt_field_icon1 != "")
912 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
913 if(sbt_field_icon2 != "")
914 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
915 pos.x -= sbt_field_size[i] + hud_fontsize.x;
920 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
923 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
926 vector h_pos = item_pos;
927 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
929 bool complete = (this_team == NUM_SPECTATOR);
932 if((sbt_highlight) && (!(pl_number % 2)))
933 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
935 vector pos = item_pos;
936 pos.x += hud_fontsize.x * 0.5;
937 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
939 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
941 width_limit -= stringwidth("...", false, hud_fontsize);
942 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
943 static float max_name_width = 0;
946 float min_fieldsize = 0;
947 float fieldpadding = hud_fontsize.x * 0.25;
948 if(this_team == NUM_SPECTATOR)
950 if(autocvar_hud_panel_scoreboard_spectators_showping)
951 min_fieldsize = stringwidth("999", false, hud_fontsize);
953 else if(autocvar_hud_panel_scoreboard_others_showscore)
954 min_fieldsize = stringwidth("99", false, hud_fontsize);
955 for(i = 0; pl; pl = pl.sort_next)
957 if(pl.team != this_team)
963 if(this_team == NUM_SPECTATOR)
965 if(autocvar_hud_panel_scoreboard_spectators_showping)
966 field = Scoreboard_GetField(pl, SP_PING);
968 else if(autocvar_hud_panel_scoreboard_others_showscore)
969 field = Scoreboard_GetField(pl, SP_SCORE);
971 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
972 float column_width = stringwidth(str, true, hud_fontsize);
973 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
975 if(column_width > max_name_width)
976 max_name_width = column_width;
977 column_width = max_name_width;
981 fieldsize = stringwidth(field, false, hud_fontsize);
982 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
985 if(pos.x + column_width > width_limit)
990 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
995 pos.x = item_pos.x + hud_fontsize.x * 0.5;
996 pos.y += hud_fontsize.y * 1.25;
1000 vector name_pos = pos;
1001 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1002 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1003 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1006 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1007 h_size.y = hud_fontsize.y;
1008 vector field_pos = pos;
1009 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1010 field_pos.x += column_width - h_size.x;
1012 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1013 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1014 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1018 h_size.x = column_width + hud_fontsize.x * 0.25;
1019 h_size.y = hud_fontsize.y;
1020 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
1022 pos.x += column_width;
1023 pos.x += hud_fontsize.x;
1025 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1028 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1030 int max_players = 999;
1031 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1033 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1036 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1037 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1038 height /= team_count;
1041 height -= panel_bg_padding * 2; // - padding
1042 max_players = floor(height / (hud_fontsize.y * 1.25));
1043 if(max_players <= 1)
1045 if(max_players == tm.team_size)
1050 entity me = playerslots[current_player];
1052 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1053 panel_size.y += panel_bg_padding * 2;
1056 vector end_pos = panel_pos + eY * (panel_size.y + 0.5* hud_fontsize.y);
1057 if(panel.current_panel_bg != "0")
1058 end_pos.y += panel_bg_border * 2;
1060 if(panel_bg_padding)
1062 panel_pos += '1 1 0' * panel_bg_padding;
1063 panel_size -= '2 2 0' * panel_bg_padding;
1067 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1071 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1073 pos.y += 1.25 * hud_fontsize.y;
1076 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1078 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1081 // print header row and highlight columns
1082 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1084 // fill the table and draw the rows
1085 bool is_self = false;
1086 bool self_shown = false;
1088 for(pl = players.sort_next; pl; pl = pl.sort_next)
1090 if(pl.team != tm.team)
1092 if(i == max_players - 2 && pl != me)
1094 if(!self_shown && me.team == tm.team)
1096 Scoreboard_DrawItem(pos, rgb, me, true, i);
1098 pos.y += 1.25 * hud_fontsize.y;
1102 if(i >= max_players - 1)
1104 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1107 is_self = (pl.sv_entnum == current_player);
1108 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1111 pos.y += 1.25 * hud_fontsize.y;
1115 panel_size.x += panel_bg_padding * 2; // restore initial width
1119 bool Scoreboard_WouldDraw()
1121 if (MUTATOR_CALLHOOK(DrawScoreboard))
1123 else if (QuickMenu_IsOpened())
1125 else if (HUD_Radar_Clickable())
1127 else if (scoreboard_showscores)
1129 else if (intermission == 1)
1131 else if (intermission == 2)
1133 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !ISGAMETYPE(CTS) && !active_minigame)
1135 else if (scoreboard_showscores_force)
1140 float average_accuracy;
1141 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1145 if (scoreboard_fade_alpha == 1)
1146 scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
1148 scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
1150 vector initial_pos = pos;
1152 WepSet weapons_stat = WepSet_GetFromStat();
1153 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1154 int disownedcnt = 0;
1156 FOREACH(Weapons, it != WEP_Null, {
1157 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1159 WepSet set = it.m_wepset;
1160 if(it.spawnflags & WEP_TYPE_OTHER)
1165 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1167 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1174 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1175 if (weapon_cnt <= 0) return pos;
1178 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1180 int columnns = ceil(weapon_cnt / rows);
1182 float weapon_height = 29;
1183 float height = hud_fontsize.y + weapon_height;
1185 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);
1186 pos.y += 1.25 * hud_fontsize.y;
1187 if(panel.current_panel_bg != "0")
1188 pos.y += panel_bg_border;
1191 panel_size.y = height * rows;
1192 panel_size.y += panel_bg_padding * 2;
1194 float panel_bg_alpha_save = panel_bg_alpha;
1195 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1197 panel_bg_alpha = panel_bg_alpha_save;
1199 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1200 if(panel.current_panel_bg != "0")
1201 end_pos.y += panel_bg_border * 2;
1203 if(panel_bg_padding)
1205 panel_pos += '1 1 0' * panel_bg_padding;
1206 panel_size -= '2 2 0' * panel_bg_padding;
1210 vector tmp = panel_size;
1212 float weapon_width = tmp.x / columnns / rows;
1215 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1219 // column highlighting
1220 for (int i = 0; i < columnns; ++i)
1222 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);
1225 for (int i = 0; i < rows; ++i)
1226 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1229 average_accuracy = 0;
1230 int weapons_with_stats = 0;
1232 pos.x += weapon_width / 2;
1234 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1237 Accuracy_LoadColors();
1239 float oldposx = pos.x;
1243 FOREACH(Weapons, it != WEP_Null, {
1244 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1246 WepSet set = it.m_wepset;
1247 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1249 if (it.spawnflags & WEP_TYPE_OTHER)
1253 if (weapon_stats >= 0)
1254 weapon_alpha = sbt_fg_alpha;
1256 weapon_alpha = 0.2 * sbt_fg_alpha;
1259 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1261 if (weapon_stats >= 0) {
1262 weapons_with_stats += 1;
1263 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1266 s = sprintf("%d%%", weapon_stats * 100);
1269 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1271 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1272 rgb = Accuracy_GetColor(weapon_stats);
1274 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1276 tmpos.x += weapon_width * rows;
1277 pos.x += weapon_width * rows;
1278 if (rows == 2 && column == columnns - 1) {
1286 if (weapons_with_stats)
1287 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1289 panel_size.x += panel_bg_padding * 2; // restore initial width
1291 if (scoreboard_acc_fade_alpha == 1)
1293 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1296 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1298 pos.x += hud_fontsize.x * 0.25;
1299 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1300 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1301 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1303 pos.y += hud_fontsize.y;
1308 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1309 float stat_secrets_found, stat_secrets_total;
1310 float stat_monsters_killed, stat_monsters_total;
1314 // get monster stats
1315 stat_monsters_killed = STAT(MONSTERS_KILLED);
1316 stat_monsters_total = STAT(MONSTERS_TOTAL);
1318 // get secrets stats
1319 stat_secrets_found = STAT(SECRETS_FOUND);
1320 stat_secrets_total = STAT(SECRETS_TOTAL);
1322 // get number of rows
1323 if(stat_secrets_total)
1325 if(stat_monsters_total)
1328 // if no rows, return
1332 // draw table header
1333 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1334 pos.y += 1.25 * hud_fontsize.y;
1335 if(panel.current_panel_bg != "0")
1336 pos.y += panel_bg_border;
1339 panel_size.y = hud_fontsize.y * rows;
1340 panel_size.y += panel_bg_padding * 2;
1343 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1344 if(panel.current_panel_bg != "0")
1345 end_pos.y += panel_bg_border * 2;
1347 if(panel_bg_padding)
1349 panel_pos += '1 1 0' * panel_bg_padding;
1350 panel_size -= '2 2 0' * panel_bg_padding;
1354 vector tmp = panel_size;
1357 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1360 if(stat_monsters_total)
1362 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1363 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1367 if(stat_secrets_total)
1369 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1370 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1373 panel_size.x += panel_bg_padding * 2; // restore initial width
1378 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1381 RANKINGS_RECEIVED_CNT = 0;
1382 for (i=RANKINGS_CNT-1; i>=0; --i)
1384 ++RANKINGS_RECEIVED_CNT;
1386 if (RANKINGS_RECEIVED_CNT == 0)
1389 vector hl_rgb = rgb + '0.5 0.5 0.5';
1391 pos.y += hud_fontsize.y;
1392 drawstring(pos + eX * panel_bg_padding, ((ISGAMETYPE(CTF)) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1393 pos.y += 1.25 * hud_fontsize.y;
1394 if(panel.current_panel_bg != "0")
1395 pos.y += panel_bg_border;
1400 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1402 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1407 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1409 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1413 float ranksize = 3 * hud_fontsize.x;
1414 float timesize = 5 * hud_fontsize.x;
1415 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1416 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1417 columns = min(columns, RANKINGS_RECEIVED_CNT);
1419 // expand name column to fill the entire row
1420 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1421 namesize += available_space;
1422 columnsize.x += available_space;
1424 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1425 panel_size.y += panel_bg_padding * 2;
1429 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1430 if(panel.current_panel_bg != "0")
1431 end_pos.y += panel_bg_border * 2;
1433 if(panel_bg_padding)
1435 panel_pos += '1 1 0' * panel_bg_padding;
1436 panel_size -= '2 2 0' * panel_bg_padding;
1442 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1444 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1446 int column = 0, j = 0;
1447 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1448 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1455 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1456 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1457 else if(!((j + column) & 1) && sbt_highlight)
1458 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1460 str = count_ordinal(i+1);
1461 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1462 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1463 str = ColorTranslateRGB(grecordholder[i]);
1465 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1466 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1468 pos.y += 1.25 * hud_fontsize.y;
1470 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1474 pos.x += panel_size.x / columns;
1475 pos.y = panel_pos.y;
1478 strfree(zoned_name_self);
1480 panel_size.x += panel_bg_padding * 2; // restore initial width
1484 float scoreboard_time;
1485 bool have_weapon_stats;
1486 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1488 if (ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || ISGAMETYPE(NEXBALL))
1490 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1493 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1494 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1500 if (!have_weapon_stats)
1502 FOREACH(Weapons, it != WEP_Null, {
1503 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1504 if (weapon_stats >= 0)
1506 have_weapon_stats = true;
1510 if (!have_weapon_stats)
1517 void Scoreboard_Draw()
1519 if(!autocvar__hud_configure)
1521 if(!hud_draw_maximized) return;
1523 // frametime checks allow to toggle the scoreboard even when the game is paused
1524 if(scoreboard_active) {
1525 if (scoreboard_fade_alpha < 1)
1526 scoreboard_time = time;
1527 if(hud_configure_menu_open == 1)
1528 scoreboard_fade_alpha = 1;
1529 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1530 if (scoreboard_fadeinspeed && frametime)
1531 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1533 scoreboard_fade_alpha = 1;
1534 if(hud_fontsize_str != autocvar_hud_fontsize)
1536 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1537 Scoreboard_initFieldSizes();
1538 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1542 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1543 if (scoreboard_fadeoutspeed && frametime)
1544 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1546 scoreboard_fade_alpha = 0;
1549 if (!scoreboard_fade_alpha)
1551 scoreboard_acc_fade_alpha = 0;
1556 scoreboard_fade_alpha = 0;
1558 if (autocvar_hud_panel_scoreboard_dynamichud)
1561 HUD_Scale_Disable();
1563 if(scoreboard_fade_alpha <= 0)
1565 panel_fade_alpha *= scoreboard_fade_alpha;
1566 HUD_Panel_LoadCvars();
1568 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1569 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1570 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1571 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1572 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1573 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1575 // don't overlap with con_notify
1576 if(!autocvar__hud_configure)
1577 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1579 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1580 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1581 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1582 panel_size.x = fixed_scoreboard_width;
1584 Scoreboard_UpdatePlayerTeams();
1586 vector pos = panel_pos;
1591 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1593 // Begin of Game Info Section
1594 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1595 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1597 // Game Info: Game Type
1598 str = MapInfo_Type_ToText(gametype);
1599 draw_beginBoldFont();
1600 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);
1603 // Game Info: Game Detail
1605 str = ""; // optionally "^7Limits: "
1606 tl = STAT(TIMELIMIT);
1607 fl = STAT(FRAGLIMIT);
1608 ll = STAT(LEADLIMIT);
1610 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1611 if(!ISGAMETYPE(LMS))
1616 str = strcat(str, "^7 / "); // delimiter
1619 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1620 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1621 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1622 TranslateScoresLabel(teamscores_label(ts_primary))));
1626 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1627 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1628 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1629 TranslateScoresLabel(scores_label(ps_primary))));
1634 if(tl > 0 || fl > 0)
1635 str = strcat(str, "^7 / "); // delimiter
1638 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1639 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1640 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1641 TranslateScoresLabel(teamscores_label(ts_primary))));
1645 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1646 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1647 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1648 TranslateScoresLabel(scores_label(ps_primary))));
1653 pos.y += sb_gameinfo_type_fontsize.y;
1654 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
1656 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1657 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1658 // End of Game Info Section
1660 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1661 if(panel.current_panel_bg != "0")
1662 pos.y += panel_bg_border;
1664 // Draw the scoreboard
1665 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1668 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1672 vector panel_bg_color_save = panel_bg_color;
1673 vector team_score_baseoffset;
1674 vector team_size_baseoffset;
1675 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1677 // put team score to the left of scoreboard (and team size to the right)
1678 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1679 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1680 if(panel.current_panel_bg != "0")
1682 team_score_baseoffset.x -= panel_bg_border;
1683 team_size_baseoffset.x += panel_bg_border;
1688 // put team score to the right of scoreboard (and team size to the left)
1689 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1690 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1691 if(panel.current_panel_bg != "0")
1693 team_score_baseoffset.x += panel_bg_border;
1694 team_size_baseoffset.x -= panel_bg_border;
1698 int team_size_total = 0;
1699 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1701 // calculate team size total (sum of all team sizes)
1702 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1703 if(tm.team != NUM_SPECTATOR)
1704 team_size_total += tm.team_size;
1707 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1709 if(tm.team == NUM_SPECTATOR)
1714 draw_beginBoldFont();
1715 vector rgb = Team_ColorRGB(tm.team);
1716 str = ftos(tm.(teamscores(ts_primary)));
1717 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1719 // team score on the left (default)
1720 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1724 // team score on the right
1725 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1727 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1729 // team size (if set to show on the side)
1730 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1732 // calculate the starting position for the whole team size info string
1733 str = sprintf("%d/%d", tm.team_size, team_size_total);
1734 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1736 // team size on the left
1737 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1741 // team size on the right
1742 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1744 str = sprintf("%d", tm.team_size);
1745 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1746 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1747 str = sprintf("/%d", team_size_total);
1748 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1752 // secondary score, e.g. keyhunt
1753 if(ts_primary != ts_secondary)
1755 str = ftos(tm.(teamscores(ts_secondary)));
1756 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1759 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1764 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1767 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1770 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1771 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1772 else if(panel_bg_color_team > 0)
1773 panel_bg_color = rgb * panel_bg_color_team;
1775 panel_bg_color = rgb;
1776 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1778 panel_bg_color = panel_bg_color_save;
1782 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1783 if(tm.team != NUM_SPECTATOR)
1786 // display it anyway
1787 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1790 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1791 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1793 if(ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || (autocvar_hud_panel_scoreboard_ctf_leaderboard && ISGAMETYPE(CTF) && STAT(CTF_SHOWLEADERBOARD))) {
1794 if(race_speedaward) {
1795 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);
1796 pos.y += 1.25 * hud_fontsize.y;
1798 if(race_speedaward_alltimebest) {
1799 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);
1800 pos.y += 1.25 * hud_fontsize.y;
1802 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1805 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1808 for(pl = players.sort_next; pl; pl = pl.sort_next)
1810 if(pl.team == NUM_SPECTATOR)
1812 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1813 if(tm.team == NUM_SPECTATOR)
1815 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1816 draw_beginBoldFont();
1817 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1819 pos.y += 1.25 * hud_fontsize.y;
1821 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1822 pos.y += 1.25 * hud_fontsize.y;
1829 // print information about respawn status
1830 float respawn_time = STAT(RESPAWN_TIME);
1834 if(respawn_time < 0)
1836 // a negative number means we are awaiting respawn, time value is still the same
1837 respawn_time *= -1; // remove mark now that we checked it
1839 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1840 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1842 str = sprintf(_("^1Respawning in ^3%s^1..."),
1843 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1844 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1846 count_seconds(ceil(respawn_time - time))
1850 else if(time < respawn_time)
1852 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1853 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1854 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1856 count_seconds(ceil(respawn_time - time))
1860 else if(time >= respawn_time)
1861 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1863 pos.y += 1.2 * hud_fontsize.y;
1864 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1867 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;