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;
36 int sbt_team_size_position;
37 int sbt_team_size_total;
39 // provide basic panel cvars to old clients
40 // TODO remove them after a future release (0.8.2+)
41 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
42 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
43 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
44 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
45 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
46 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
47 noref string autocvar_hud_panel_scoreboard_bg_border = "";
48 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
50 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
51 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
52 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
53 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
54 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
55 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
56 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
57 bool autocvar_hud_panel_scoreboard_table_highlight = true;
58 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
59 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
60 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
61 float autocvar_hud_panel_scoreboard_namesize = 15;
62 float autocvar_hud_panel_scoreboard_team_size_position = 0;
64 bool autocvar_hud_panel_scoreboard_accuracy = true;
65 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
66 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
67 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
68 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
70 bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
72 bool autocvar_hud_panel_scoreboard_dynamichud = false;
74 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
75 bool autocvar_hud_panel_scoreboard_others_showscore = true;
76 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
77 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
78 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
80 // mode 0: returns translated label
81 // mode 1: prints name and description of all the labels
82 string Label_getInfo(string label, int mode)
85 label = "bckills"; // first case in the switch
89 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_INFO(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
90 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_INFO(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
91 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")));
92 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_INFO(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
93 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_INFO(strcat("^3", "deaths", " ^7", _("Number of deaths")));
94 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_INFO(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
95 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_INFO(strcat("^3", "dmg", " ^7", _("The total damage done")));
96 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_INFO(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
97 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_INFO(strcat("^3", "drops", " ^7", _("Number of flag drops")));
98 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_INFO(strcat("^3", "elo", " ^7", _("Player ELO")));
99 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_INFO(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
100 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_INFO(strcat("^3", "faults", " ^7", _("Number of faults committed")));
101 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_INFO(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
102 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_INFO(strcat("^3", "fps", " ^7", _("FPS")));
103 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_INFO(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
104 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_INFO(strcat("^3", "goals", " ^7", _("Number of goals scored")));
105 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_INFO(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
106 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_INFO(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
107 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_INFO(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
108 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_INFO(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
109 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_INFO(strcat("^3", "kills", " ^7", _("Number of kills")));
110 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_INFO(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
111 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_INFO(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
112 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_INFO(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
113 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_INFO(strcat("^3", "name", " ^7", _("Player name")));
114 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_INFO(strcat("^3", "nick", " ^7", _("Player name")));
115 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_INFO(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
116 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")));
117 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_INFO(strcat("^3", "ping", " ^7", _("Ping time")));
118 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_INFO(strcat("^3", "pl", " ^7", _("Packet loss")));
119 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_INFO(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
120 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_INFO(strcat("^3", "rank", " ^7", _("Player rank")));
121 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_INFO(strcat("^3", "returns", " ^7", _("Number of flag returns")));
122 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_INFO(strcat("^3", "revivals", " ^7", _("Number of revivals")));
123 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_INFO(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
124 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_INFO(strcat("^3", "score", " ^7", _("Total score")));
125 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_INFO(strcat("^3", "suicides", " ^7", _("Number of suicides")));
126 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_INFO(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
127 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_INFO(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
128 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_INFO(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
129 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_INFO(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
130 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_INFO(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
131 default: return label;
136 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
137 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
139 void Scoreboard_InitScores()
143 ps_primary = ps_secondary = NULL;
144 ts_primary = ts_secondary = -1;
145 FOREACH(Scores, true, {
146 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
147 if(f == SFL_SORT_PRIO_PRIMARY)
149 if(f == SFL_SORT_PRIO_SECONDARY)
152 if(ps_secondary == NULL)
153 ps_secondary = ps_primary;
155 for(i = 0; i < MAX_TEAMSCORE; ++i)
157 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
158 if(f == SFL_SORT_PRIO_PRIMARY)
160 if(f == SFL_SORT_PRIO_SECONDARY)
163 if(ts_secondary == -1)
164 ts_secondary = ts_primary;
166 Cmd_Scoreboard_SetFields(0);
170 void Scoreboard_UpdatePlayerTeams()
174 for(pl = players.sort_next; pl; pl = pl.sort_next)
177 int Team = entcs_GetScoreTeam(pl.sv_entnum);
178 if(SetTeam(pl, Team))
181 Scoreboard_UpdatePlayerPos(pl);
185 pl = players.sort_next;
190 print(strcat("PNUM: ", ftos(num), "\n"));
195 int Scoreboard_CompareScore(int vl, int vr, int f)
197 TC(int, vl); TC(int, vr); TC(int, f);
198 if(f & SFL_ZERO_IS_WORST)
200 if(vl == 0 && vr != 0)
202 if(vl != 0 && vr == 0)
206 return IS_INCREASING(f);
208 return IS_DECREASING(f);
212 float Scoreboard_ComparePlayerScores(entity left, entity right)
215 vl = entcs_GetTeam(left.sv_entnum);
216 vr = entcs_GetTeam(right.sv_entnum);
228 if(vl == NUM_SPECTATOR)
230 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
232 if(!left.gotscores && right.gotscores)
237 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
241 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
245 FOREACH(Scores, true, {
246 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
247 if (r >= 0) return r;
250 if (left.sv_entnum < right.sv_entnum)
256 void Scoreboard_UpdatePlayerPos(entity player)
259 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
261 SORT_SWAP(player, ent);
263 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
265 SORT_SWAP(ent, player);
269 float Scoreboard_CompareTeamScores(entity left, entity right)
273 if(left.team == NUM_SPECTATOR)
275 if(right.team == NUM_SPECTATOR)
278 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
282 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
286 for(i = 0; i < MAX_TEAMSCORE; ++i)
288 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
293 if (left.team < right.team)
299 void Scoreboard_UpdateTeamPos(entity Team)
302 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
304 SORT_SWAP(Team, ent);
306 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
308 SORT_SWAP(ent, Team);
312 void Cmd_Scoreboard_Help()
314 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
315 LOG_INFO(_("Usage:"));
316 LOG_INFO("^2scoreboard_columns_set ^3default");
317 LOG_INFO(_("^2scoreboard_columns_set ^3field1 field2 ..."));
318 LOG_INFO(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
319 LOG_INFO(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
320 LOG_INFO(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
321 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields."));
322 LOG_INFO(_("The following field names are recognized (case insensitive):"));
328 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
329 "of game types, then a slash, to make the field show up only in these\n"
330 "or in all but these game types. You can also specify 'all' as a\n"
331 "field to show all fields available for the current game mode."));
334 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
335 "include/exclude ALL teams/noteams game modes."));
338 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
339 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
340 "right of the vertical bar aligned to the right."));
341 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
342 "other gamemodes except DM."));
345 // NOTE: adding a gametype with ? to not warn for an optional field
346 // make sure it's excluded in a previous exclusive rule, if any
347 // otherwise the previous exclusive rule warns anyway
348 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
349 #define SCOREBOARD_DEFAULT_COLUMNS \
350 "ping pl fps name |" \
351 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
352 " -teams,lms/deaths +ft,tdm/deaths" \
354 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
355 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
356 " +tdm,ft,dom,ons,as/teamkills"\
357 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
358 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
359 " +lms/lives +lms/rank" \
360 " +kh/kckills +kh/losses +kh/caps" \
361 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
362 " +as/objectives +nb/faults +nb/goals" \
363 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
364 " +dom/ticks +dom/takes" \
365 " -lms,rc,cts,inv,nb/score"
367 void Cmd_Scoreboard_SetFields(int argc)
372 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
376 return; // do nothing, we don't know gametype and scores yet
378 // sbt_fields uses strunzone on the titles!
379 if(!sbt_field_title[0])
380 for(i = 0; i < MAX_SBT_FIELDS; ++i)
381 sbt_field_title[i] = strzone("(null)");
383 // TODO: re enable with gametype dependant cvars?
384 if(argc < 3) // no arguments provided
385 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
388 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
392 if(argv(2) == "default" || argv(2) == "expand_default")
394 if(argv(2) == "expand_default")
395 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
396 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
398 else if(argv(2) == "all")
400 string s = "ping pl name |"; // scores without a label
401 FOREACH(Scores, true, {
403 if(it != ps_secondary)
404 if(scores_label(it) != "")
405 s = strcat(s, " ", scores_label(it));
407 if(ps_secondary != ps_primary)
408 s = strcat(s, " ", scores_label(ps_secondary));
409 s = strcat(s, " ", scores_label(ps_primary));
410 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
417 hud_fontsize = HUD_GetFontsize("hud_fontsize");
419 for(i = 1; i < argc - 1; ++i)
422 bool nocomplain = false;
423 if(substring(str, 0, 1) == "?")
426 str = substring(str, 1, strlen(str) - 1);
429 slash = strstrofs(str, "/", 0);
432 pattern = substring(str, 0, slash);
433 str = substring(str, slash + 1, strlen(str) - (slash + 1));
435 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
439 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
440 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
441 str = strtolower(str);
446 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
447 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
448 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
449 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
450 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
451 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
452 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
453 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
454 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
455 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
458 FOREACH(Scores, true, {
459 if (str == strtolower(scores_label(it))) {
461 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
471 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
475 sbt_field[sbt_num_fields] = j;
478 if(j == ps_secondary)
479 have_secondary = true;
484 if(sbt_num_fields >= MAX_SBT_FIELDS)
488 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
490 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
491 have_secondary = true;
492 if(ps_primary == ps_secondary)
493 have_secondary = true;
494 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
496 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
500 strunzone(sbt_field_title[sbt_num_fields]);
501 for(i = sbt_num_fields; i > 0; --i)
503 sbt_field_title[i] = sbt_field_title[i-1];
504 sbt_field_size[i] = sbt_field_size[i-1];
505 sbt_field[i] = sbt_field[i-1];
507 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
508 sbt_field[0] = SP_NAME;
510 LOG_INFO("fixed missing field 'name'");
514 strunzone(sbt_field_title[sbt_num_fields]);
515 for(i = sbt_num_fields; i > 1; --i)
517 sbt_field_title[i] = sbt_field_title[i-1];
518 sbt_field_size[i] = sbt_field_size[i-1];
519 sbt_field[i] = sbt_field[i-1];
521 sbt_field_title[1] = strzone("|");
522 sbt_field[1] = SP_SEPARATOR;
523 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
525 LOG_INFO("fixed missing field '|'");
528 else if(!have_separator)
530 strcpy(sbt_field_title[sbt_num_fields], "|");
531 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
532 sbt_field[sbt_num_fields] = SP_SEPARATOR;
534 LOG_INFO("fixed missing field '|'");
538 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
539 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
540 sbt_field[sbt_num_fields] = ps_secondary;
542 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
546 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
547 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
548 sbt_field[sbt_num_fields] = ps_primary;
550 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
554 sbt_field[sbt_num_fields] = SP_END;
558 vector sbt_field_rgb;
559 string sbt_field_icon0;
560 string sbt_field_icon1;
561 string sbt_field_icon2;
562 vector sbt_field_icon0_rgb;
563 vector sbt_field_icon1_rgb;
564 vector sbt_field_icon2_rgb;
565 string sbt_font_self;
566 string Scoreboard_GetName(entity pl)
568 if(ready_waiting && pl.ready)
570 sbt_field_icon0 = "gfx/scoreboard/player_ready";
574 int f = entcs_GetClientColors(pl.sv_entnum);
576 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
577 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
578 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
579 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
580 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
583 return entcs_GetName(pl.sv_entnum);
585 string Scoreboard_GetField(entity pl, PlayerScoreField field)
587 float tmp, num, denom;
590 sbt_field_rgb = '1 1 1';
591 sbt_field_icon0 = "";
592 sbt_field_icon1 = "";
593 sbt_field_icon2 = "";
594 sbt_field_icon0_rgb = '1 1 1';
595 sbt_field_icon1_rgb = '1 1 1';
596 sbt_field_icon2_rgb = '1 1 1';
601 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
602 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
606 tmp = max(0, min(220, f-80)) / 220;
607 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
613 f = pl.ping_packetloss;
614 tmp = pl.ping_movementloss;
615 if(f == 0 && tmp == 0)
617 str = ftos(ceil(f * 100));
619 str = strcat(str, "~", ftos(ceil(tmp * 100)));
620 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
621 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
625 return Scoreboard_GetName(pl);
628 f = pl.(scores(SP_KILLS));
629 f -= pl.(scores(SP_SUICIDES));
633 num = pl.(scores(SP_KILLS));
634 denom = pl.(scores(SP_DEATHS));
637 sbt_field_rgb = '0 1 0';
638 str = sprintf("%d", num);
639 } else if(num <= 0) {
640 sbt_field_rgb = '1 0 0';
641 str = sprintf("%.1f", num/denom);
643 str = sprintf("%.1f", num/denom);
647 f = pl.(scores(SP_KILLS));
648 f -= pl.(scores(SP_DEATHS));
651 sbt_field_rgb = '0 1 0';
653 sbt_field_rgb = '1 1 1';
655 sbt_field_rgb = '1 0 0';
661 float elo = pl.(scores(SP_ELO));
663 case -1: return "...";
664 case -2: return _("N/A");
665 default: return ftos(elo);
671 float fps = pl.(scores(SP_FPS));
674 sbt_field_rgb = '1 1 1';
675 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
677 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200);
678 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
682 case SP_DMG: case SP_DMGTAKEN:
683 return sprintf("%.1f k", pl.(scores(field)) / 1000);
685 default: case SP_SCORE:
686 tmp = pl.(scores(field));
687 f = scores_flags(field);
688 if(field == ps_primary)
689 sbt_field_rgb = '1 1 0';
690 else if(field == ps_secondary)
691 sbt_field_rgb = '0 1 1';
693 sbt_field_rgb = '1 1 1';
694 return ScoreString(f, tmp);
699 float sbt_fixcolumnwidth_len;
700 float sbt_fixcolumnwidth_iconlen;
701 float sbt_fixcolumnwidth_marginlen;
703 string Scoreboard_FixColumnWidth(int i, string str)
709 sbt_fixcolumnwidth_iconlen = 0;
711 if(sbt_field_icon0 != "")
713 sz = draw_getimagesize(sbt_field_icon0);
715 if(sbt_fixcolumnwidth_iconlen < f)
716 sbt_fixcolumnwidth_iconlen = f;
719 if(sbt_field_icon1 != "")
721 sz = draw_getimagesize(sbt_field_icon1);
723 if(sbt_fixcolumnwidth_iconlen < f)
724 sbt_fixcolumnwidth_iconlen = f;
727 if(sbt_field_icon2 != "")
729 sz = draw_getimagesize(sbt_field_icon2);
731 if(sbt_fixcolumnwidth_iconlen < f)
732 sbt_fixcolumnwidth_iconlen = f;
735 if(sbt_fixcolumnwidth_iconlen != 0)
737 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
738 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
741 sbt_fixcolumnwidth_marginlen = 0;
743 if(sbt_field[i] == SP_NAME) // name gets all remaining space
746 float remaining_space = 0;
747 for(j = 0; j < sbt_num_fields; ++j)
749 if (sbt_field[i] != SP_SEPARATOR)
750 remaining_space += sbt_field_size[j] + hud_fontsize.x;
751 sbt_field_size[i] = panel_size.x - remaining_space;
753 if (sbt_fixcolumnwidth_iconlen != 0)
754 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
755 float namesize = panel_size.x - remaining_space;
756 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
757 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
759 max_namesize = vid_conwidth - remaining_space;
762 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
764 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
765 if(sbt_field_size[i] < f)
766 sbt_field_size[i] = f;
771 void Scoreboard_initFieldSizes()
773 for(int i = 0; i < sbt_num_fields; ++i)
775 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
776 Scoreboard_FixColumnWidth(i, "");
780 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
783 vector column_dim = eY * panel_size.y;
785 column_dim.y -= 1.25 * hud_fontsize.y;
786 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
787 pos.x += hud_fontsize.x * 0.5;
788 for(i = 0; i < sbt_num_fields; ++i)
790 if(sbt_field[i] == SP_SEPARATOR)
792 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
795 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
796 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
797 pos.x += column_dim.x;
799 if(sbt_field[i] == SP_SEPARATOR)
801 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
802 for(i = sbt_num_fields - 1; i > 0; --i)
804 if(sbt_field[i] == SP_SEPARATOR)
807 pos.x -= sbt_field_size[i];
812 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
813 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
816 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
817 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
818 pos.x -= hud_fontsize.x;
823 pos.y += 1.25 * hud_fontsize.y;
827 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
829 TC(bool, is_self); TC(int, pl_number);
831 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
833 vector h_pos = item_pos;
834 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
835 // alternated rows highlighting
837 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
838 else if((sbt_highlight) && (!(pl_number % 2)))
839 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
841 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
843 vector pos = item_pos;
844 // put a "self indicator" beside the self row
845 sbt_font_self = "â—€"; // unicode U+25C0 (black left-pointing triangle)
847 drawstring(pos+eX*(panel_size.x+.5*hud_fontsize.x)+eY, sbt_font_self, vec2(hud_fontsize.x, hud_fontsize.y), rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
849 pos.x += hud_fontsize.x * 0.5;
850 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
851 vector tmp = '0 0 0';
853 PlayerScoreField field;
854 for(i = 0; i < sbt_num_fields; ++i)
856 field = sbt_field[i];
857 if(field == SP_SEPARATOR)
860 if(is_spec && field != SP_NAME && field != SP_PING) {
861 pos.x += sbt_field_size[i] + hud_fontsize.x;
864 str = Scoreboard_GetField(pl, field);
865 str = Scoreboard_FixColumnWidth(i, str);
867 pos.x += sbt_field_size[i] + hud_fontsize.x;
869 if(field == SP_NAME) {
870 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
871 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
873 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
874 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
877 tmp.x = sbt_field_size[i] + hud_fontsize.x;
878 if(sbt_field_icon0 != "")
879 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
880 if(sbt_field_icon1 != "")
881 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
882 if(sbt_field_icon2 != "")
883 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
886 if(sbt_field[i] == SP_SEPARATOR)
888 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
889 for(i = sbt_num_fields-1; i > 0; --i)
891 field = sbt_field[i];
892 if(field == SP_SEPARATOR)
895 if(is_spec && field != SP_NAME && field != SP_PING) {
896 pos.x -= sbt_field_size[i] + hud_fontsize.x;
900 str = Scoreboard_GetField(pl, field);
901 str = Scoreboard_FixColumnWidth(i, str);
903 if(field == SP_NAME) {
904 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
905 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
907 tmp.x = sbt_fixcolumnwidth_len;
908 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
911 tmp.x = sbt_field_size[i];
912 if(sbt_field_icon0 != "")
913 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
914 if(sbt_field_icon1 != "")
915 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
916 if(sbt_field_icon2 != "")
917 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
918 pos.x -= sbt_field_size[i] + hud_fontsize.x;
923 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
926 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
929 vector h_pos = item_pos;
930 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
932 bool complete = (this_team == NUM_SPECTATOR);
935 if((sbt_highlight) && (!(pl_number % 2)))
936 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
938 vector pos = item_pos;
939 pos.x += hud_fontsize.x * 0.5;
940 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
942 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
944 width_limit -= stringwidth("...", false, hud_fontsize);
945 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
946 static float max_name_width = 0;
949 float min_fieldsize = 0;
950 float fieldpadding = hud_fontsize.x * 0.25;
951 if(this_team == NUM_SPECTATOR)
953 if(autocvar_hud_panel_scoreboard_spectators_showping)
954 min_fieldsize = stringwidth("999", false, hud_fontsize);
956 else if(autocvar_hud_panel_scoreboard_others_showscore)
957 min_fieldsize = stringwidth("99", false, hud_fontsize);
958 for(i = 0; pl; pl = pl.sort_next)
960 if(pl.team != this_team)
966 if(this_team == NUM_SPECTATOR)
968 if(autocvar_hud_panel_scoreboard_spectators_showping)
969 field = Scoreboard_GetField(pl, SP_PING);
971 else if(autocvar_hud_panel_scoreboard_others_showscore)
972 field = Scoreboard_GetField(pl, SP_SCORE);
974 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
975 float column_width = stringwidth(str, true, hud_fontsize);
976 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
978 if(column_width > max_name_width)
979 max_name_width = column_width;
980 column_width = max_name_width;
984 fieldsize = stringwidth(field, false, hud_fontsize);
985 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
988 if(pos.x + column_width > width_limit)
993 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
998 pos.x = item_pos.x + hud_fontsize.x * 0.5;
999 pos.y += hud_fontsize.y * 1.25;
1003 vector name_pos = pos;
1004 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1005 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1006 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1009 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1010 h_size.y = hud_fontsize.y;
1011 vector field_pos = pos;
1012 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1013 field_pos.x += column_width - h_size.x;
1015 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1016 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1017 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1021 h_size.x = column_width + hud_fontsize.x * 0.25;
1022 h_size.y = hud_fontsize.y;
1023 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
1025 pos.x += column_width;
1026 pos.x += hud_fontsize.x;
1028 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1031 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1033 int max_players = 999;
1034 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1036 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1039 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1040 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1041 height /= team_count;
1044 height -= panel_bg_padding * 2; // - padding
1045 max_players = floor(height / (hud_fontsize.y * 1.25));
1046 if(max_players <= 1)
1048 if(max_players == tm.team_size)
1053 entity me = playerslots[current_player];
1055 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1056 panel_size.y += panel_bg_padding * 2;
1059 vector end_pos = panel_pos + eY * (panel_size.y + 0.5* hud_fontsize.y);
1060 if(panel.current_panel_bg != "0")
1061 end_pos.y += panel_bg_border * 2;
1063 if(panel_bg_padding)
1065 panel_pos += '1 1 0' * panel_bg_padding;
1066 panel_size -= '2 2 0' * panel_bg_padding;
1070 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1074 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1076 pos.y += 1.25 * hud_fontsize.y;
1079 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1081 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1084 // print header row and highlight columns
1085 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1087 // fill the table and draw the rows
1088 bool is_self = false;
1089 bool self_shown = false;
1091 for(pl = players.sort_next; pl; pl = pl.sort_next)
1093 if(pl.team != tm.team)
1095 if(i == max_players - 2 && pl != me)
1097 if(!self_shown && me.team == tm.team)
1099 Scoreboard_DrawItem(pos, rgb, me, true, i);
1101 pos.y += 1.25 * hud_fontsize.y;
1105 if(i >= max_players - 1)
1107 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1110 is_self = (pl.sv_entnum == current_player);
1111 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1114 pos.y += 1.25 * hud_fontsize.y;
1118 panel_size.x += panel_bg_padding * 2; // restore initial width
1122 bool Scoreboard_WouldDraw()
1124 if (MUTATOR_CALLHOOK(DrawScoreboard))
1126 else if (QuickMenu_IsOpened())
1128 else if (HUD_Radar_Clickable())
1130 else if (scoreboard_showscores)
1132 else if (intermission == 1)
1134 else if (intermission == 2)
1136 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !ISGAMETYPE(CTS) && !active_minigame)
1138 else if (scoreboard_showscores_force)
1143 float average_accuracy;
1144 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1148 if (scoreboard_fade_alpha == 1)
1149 scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
1151 scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
1153 vector initial_pos = pos;
1155 WepSet weapons_stat = WepSet_GetFromStat();
1156 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1157 int disownedcnt = 0;
1159 FOREACH(Weapons, it != WEP_Null, {
1160 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1162 WepSet set = it.m_wepset;
1163 if(it.spawnflags & WEP_TYPE_OTHER)
1168 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1170 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1177 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1178 if (weapon_cnt <= 0) return pos;
1181 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1183 int columnns = ceil(weapon_cnt / rows);
1185 float weapon_height = 29;
1186 float height = hud_fontsize.y + weapon_height;
1188 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);
1189 pos.y += 1.25 * hud_fontsize.y;
1190 if(panel.current_panel_bg != "0")
1191 pos.y += panel_bg_border;
1194 panel_size.y = height * rows;
1195 panel_size.y += panel_bg_padding * 2;
1197 float panel_bg_alpha_save = panel_bg_alpha;
1198 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1200 panel_bg_alpha = panel_bg_alpha_save;
1202 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1203 if(panel.current_panel_bg != "0")
1204 end_pos.y += panel_bg_border * 2;
1206 if(panel_bg_padding)
1208 panel_pos += '1 1 0' * panel_bg_padding;
1209 panel_size -= '2 2 0' * panel_bg_padding;
1213 vector tmp = panel_size;
1215 float weapon_width = tmp.x / columnns / rows;
1218 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1222 // column highlighting
1223 for (int i = 0; i < columnns; ++i)
1225 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);
1228 for (int i = 0; i < rows; ++i)
1229 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1232 average_accuracy = 0;
1233 int weapons_with_stats = 0;
1235 pos.x += weapon_width / 2;
1237 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1240 Accuracy_LoadColors();
1242 float oldposx = pos.x;
1246 FOREACH(Weapons, it != WEP_Null, {
1247 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1249 WepSet set = it.m_wepset;
1250 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1252 if (it.spawnflags & WEP_TYPE_OTHER)
1256 if (weapon_stats >= 0)
1257 weapon_alpha = sbt_fg_alpha;
1259 weapon_alpha = 0.2 * sbt_fg_alpha;
1262 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1264 if (weapon_stats >= 0) {
1265 weapons_with_stats += 1;
1266 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1269 s = sprintf("%d%%", weapon_stats * 100);
1272 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1274 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1275 rgb = Accuracy_GetColor(weapon_stats);
1277 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1279 tmpos.x += weapon_width * rows;
1280 pos.x += weapon_width * rows;
1281 if (rows == 2 && column == columnns - 1) {
1289 if (weapons_with_stats)
1290 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1292 panel_size.x += panel_bg_padding * 2; // restore initial width
1294 if (scoreboard_acc_fade_alpha == 1)
1296 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1299 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1301 pos.x += hud_fontsize.x * 0.25;
1302 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1303 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1304 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1306 pos.y += hud_fontsize.y;
1311 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1312 float stat_secrets_found, stat_secrets_total;
1313 float stat_monsters_killed, stat_monsters_total;
1317 // get monster stats
1318 stat_monsters_killed = STAT(MONSTERS_KILLED);
1319 stat_monsters_total = STAT(MONSTERS_TOTAL);
1321 // get secrets stats
1322 stat_secrets_found = STAT(SECRETS_FOUND);
1323 stat_secrets_total = STAT(SECRETS_TOTAL);
1325 // get number of rows
1326 if(stat_secrets_total)
1328 if(stat_monsters_total)
1331 // if no rows, return
1335 // draw table header
1336 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1337 pos.y += 1.25 * hud_fontsize.y;
1338 if(panel.current_panel_bg != "0")
1339 pos.y += panel_bg_border;
1342 panel_size.y = hud_fontsize.y * rows;
1343 panel_size.y += panel_bg_padding * 2;
1346 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1347 if(panel.current_panel_bg != "0")
1348 end_pos.y += panel_bg_border * 2;
1350 if(panel_bg_padding)
1352 panel_pos += '1 1 0' * panel_bg_padding;
1353 panel_size -= '2 2 0' * panel_bg_padding;
1357 vector tmp = panel_size;
1360 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1363 if(stat_monsters_total)
1365 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1366 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1370 if(stat_secrets_total)
1372 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1373 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1376 panel_size.x += panel_bg_padding * 2; // restore initial width
1381 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1384 RANKINGS_RECEIVED_CNT = 0;
1385 for (i=RANKINGS_CNT-1; i>=0; --i)
1387 ++RANKINGS_RECEIVED_CNT;
1389 if (RANKINGS_RECEIVED_CNT == 0)
1392 vector hl_rgb = rgb + '0.5 0.5 0.5';
1394 pos.y += hud_fontsize.y;
1395 drawstring(pos + eX * panel_bg_padding, ((ISGAMETYPE(CTF)) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1396 pos.y += 1.25 * hud_fontsize.y;
1397 if(panel.current_panel_bg != "0")
1398 pos.y += panel_bg_border;
1403 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1405 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1410 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1412 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1416 float ranksize = 3 * hud_fontsize.x;
1417 float timesize = 5 * hud_fontsize.x;
1418 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1419 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1420 columns = min(columns, RANKINGS_RECEIVED_CNT);
1422 // expand name column to fill the entire row
1423 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1424 namesize += available_space;
1425 columnsize.x += available_space;
1427 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1428 panel_size.y += panel_bg_padding * 2;
1432 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1433 if(panel.current_panel_bg != "0")
1434 end_pos.y += panel_bg_border * 2;
1436 if(panel_bg_padding)
1438 panel_pos += '1 1 0' * panel_bg_padding;
1439 panel_size -= '2 2 0' * panel_bg_padding;
1445 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1447 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1449 int column = 0, j = 0;
1450 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1451 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1458 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1459 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1460 else if(!((j + column) & 1) && sbt_highlight)
1461 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1463 str = count_ordinal(i+1);
1464 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1465 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1466 str = ColorTranslateRGB(grecordholder[i]);
1468 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1469 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1471 pos.y += 1.25 * hud_fontsize.y;
1473 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1477 pos.x += panel_size.x / columns;
1478 pos.y = panel_pos.y;
1481 strfree(zoned_name_self);
1483 panel_size.x += panel_bg_padding * 2; // restore initial width
1487 float scoreboard_time;
1488 bool have_weapon_stats;
1489 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1491 if (ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || ISGAMETYPE(NEXBALL))
1493 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1496 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1497 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1503 if (!have_weapon_stats)
1505 FOREACH(Weapons, it != WEP_Null, {
1506 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1507 if (weapon_stats >= 0)
1509 have_weapon_stats = true;
1513 if (!have_weapon_stats)
1520 void Scoreboard_Draw()
1522 if(!autocvar__hud_configure)
1524 if(!hud_draw_maximized) return;
1526 // frametime checks allow to toggle the scoreboard even when the game is paused
1527 if(scoreboard_active) {
1528 if (scoreboard_fade_alpha < 1)
1529 scoreboard_time = time;
1530 if(hud_configure_menu_open == 1)
1531 scoreboard_fade_alpha = 1;
1532 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1533 if (scoreboard_fadeinspeed && frametime)
1534 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1536 scoreboard_fade_alpha = 1;
1537 if(hud_fontsize_str != autocvar_hud_fontsize)
1539 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1540 Scoreboard_initFieldSizes();
1541 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1545 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1546 if (scoreboard_fadeoutspeed && frametime)
1547 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1549 scoreboard_fade_alpha = 0;
1552 if (!scoreboard_fade_alpha)
1554 scoreboard_acc_fade_alpha = 0;
1559 scoreboard_fade_alpha = 0;
1561 if (autocvar_hud_panel_scoreboard_dynamichud)
1564 HUD_Scale_Disable();
1566 if(scoreboard_fade_alpha <= 0)
1568 panel_fade_alpha *= scoreboard_fade_alpha;
1569 HUD_Panel_LoadCvars();
1571 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1572 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1573 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1574 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1575 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1576 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1577 sbt_team_size_position = autocvar_hud_panel_scoreboard_team_size_position;
1579 // don't overlap with con_notify
1580 if(!autocvar__hud_configure)
1581 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1583 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1584 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1585 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1586 panel_size.x = fixed_scoreboard_width;
1588 Scoreboard_UpdatePlayerTeams();
1590 vector pos = panel_pos;
1595 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1597 // Begin of Game Info Section
1598 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1599 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1601 // Game Info: Game Type
1602 str = MapInfo_Type_ToText(gametype);
1603 draw_beginBoldFont();
1604 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);
1607 // Game Info: Game Detail
1609 str = ""; // optionally "^7Limits: "
1610 tl = STAT(TIMELIMIT);
1611 fl = STAT(FRAGLIMIT);
1612 ll = STAT(LEADLIMIT);
1614 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1615 if(!ISGAMETYPE(LMS))
1620 str = strcat(str, "^7 / "); // delimiter
1623 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1624 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1625 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1626 TranslateScoresLabel(teamscores_label(ts_primary))));
1630 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1631 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1632 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1633 TranslateScoresLabel(scores_label(ps_primary))));
1638 if(tl > 0 || fl > 0)
1639 str = strcat(str, "^7 / "); // delimiter
1642 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1643 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1644 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1645 TranslateScoresLabel(teamscores_label(ts_primary))));
1649 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1650 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1651 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1652 TranslateScoresLabel(scores_label(ps_primary))));
1657 pos.y += sb_gameinfo_type_fontsize.y;
1658 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
1660 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1661 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1662 // End of Game Info Section
1664 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1665 if(panel.current_panel_bg != "0")
1666 pos.y += panel_bg_border;
1668 // calculate team size total (sum of all team sizes)
1669 sbt_team_size_total = 0;
1670 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1671 sbt_team_size_total += tm.team_size;
1673 // Draw the scoreboard
1674 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1677 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1681 vector panel_bg_color_save = panel_bg_color;
1682 vector team_score_baseoffset;
1683 vector team_size_baseoffset;
1684 if (sbt_team_size_position != 1) // team size not on left
1686 // put team score to the left of scoreboard (and team size to the right)
1687 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1688 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1689 if(panel.current_panel_bg != "0")
1691 team_score_baseoffset.x -= panel_bg_border;
1692 team_size_baseoffset.x += panel_bg_border;
1697 // put team score to the right of scoreboard (and team size to the left)
1698 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1699 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1700 if(panel.current_panel_bg != "0")
1702 team_score_baseoffset.x += panel_bg_border;
1703 team_size_baseoffset.x -= panel_bg_border;
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 (sbt_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 (sbt_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, sbt_team_size_total);
1734 if (sbt_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", sbt_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 (sbt_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)
1785 // display it anyway
1786 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1789 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1790 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1792 if(ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || (autocvar_hud_panel_scoreboard_ctf_leaderboard && ISGAMETYPE(CTF) && STAT(CTF_SHOWLEADERBOARD))) {
1793 if(race_speedaward) {
1794 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);
1795 pos.y += 1.25 * hud_fontsize.y;
1797 if(race_speedaward_alltimebest) {
1798 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);
1799 pos.y += 1.25 * hud_fontsize.y;
1801 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1804 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1807 for(pl = players.sort_next; pl; pl = pl.sort_next)
1809 if(pl.team == NUM_SPECTATOR)
1811 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1812 if(tm.team == NUM_SPECTATOR)
1814 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1815 draw_beginBoldFont();
1816 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1818 pos.y += 1.25 * hud_fontsize.y;
1820 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1821 pos.y += 1.25 * hud_fontsize.y;
1828 // print information about respawn status
1829 float respawn_time = STAT(RESPAWN_TIME);
1833 if(respawn_time < 0)
1835 // a negative number means we are awaiting respawn, time value is still the same
1836 respawn_time *= -1; // remove mark now that we checked it
1838 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1839 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1841 str = sprintf(_("^1Respawning in ^3%s^1..."),
1842 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1843 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1845 count_seconds(ceil(respawn_time - time))
1849 else if(time < respawn_time)
1851 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1852 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1853 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1855 count_seconds(ceil(respawn_time - time))
1859 else if(time >= respawn_time)
1860 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1862 pos.y += 1.2 * hud_fontsize.y;
1863 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1866 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;