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)
1134 && (!HUD_MinigameMenu_IsOpened() || !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 | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
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;
1578 // don't overlap with con_notify
1579 if(!autocvar__hud_configure)
1580 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1582 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1583 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1584 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1585 panel_size.x = fixed_scoreboard_width;
1587 Scoreboard_UpdatePlayerTeams();
1589 vector pos = panel_pos;
1594 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1596 // Begin of Game Info Section
1597 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1598 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1600 // Game Info: Game Type
1601 str = MapInfo_Type_ToText(gametype);
1602 draw_beginBoldFont();
1603 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);
1606 // Game Info: Game Detail
1608 str = ""; // optionally "^7Limits: "
1609 tl = STAT(TIMELIMIT);
1610 fl = STAT(FRAGLIMIT);
1611 ll = STAT(LEADLIMIT);
1613 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1614 if(!ISGAMETYPE(LMS))
1619 str = strcat(str, "^7 / "); // delimiter
1622 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1623 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1624 (teamscores_label(ts_primary) == "fastest") ? "" :
1625 TranslateScoresLabel(teamscores_label(ts_primary))));
1629 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1630 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1631 (scores_label(ps_primary) == "fastest") ? "" :
1632 TranslateScoresLabel(scores_label(ps_primary))));
1637 if(tl > 0 || fl > 0)
1638 str = strcat(str, "^7 / "); // delimiter
1641 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1642 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1643 (teamscores_label(ts_primary) == "fastest") ? "" :
1644 TranslateScoresLabel(teamscores_label(ts_primary))));
1648 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1649 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1650 (scores_label(ps_primary) == "fastest") ? "" :
1651 TranslateScoresLabel(scores_label(ps_primary))));
1656 pos.y += sb_gameinfo_type_fontsize.y;
1657 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
1659 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1660 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1661 // End of Game Info Section
1663 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1664 if(panel.current_panel_bg != "0")
1665 pos.y += panel_bg_border;
1667 // Draw the scoreboard
1668 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1671 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1675 vector panel_bg_color_save = panel_bg_color;
1676 vector team_score_baseoffset;
1677 vector team_size_baseoffset;
1678 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1680 // put team score to the left of scoreboard (and team size to the right)
1681 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1682 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1683 if(panel.current_panel_bg != "0")
1685 team_score_baseoffset.x -= panel_bg_border;
1686 team_size_baseoffset.x += panel_bg_border;
1691 // put team score to the right of scoreboard (and team size to the left)
1692 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1693 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1694 if(panel.current_panel_bg != "0")
1696 team_score_baseoffset.x += panel_bg_border;
1697 team_size_baseoffset.x -= panel_bg_border;
1701 int team_size_total = 0;
1702 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1704 // calculate team size total (sum of all team sizes)
1705 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1706 if(tm.team != NUM_SPECTATOR)
1707 team_size_total += tm.team_size;
1710 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1712 if(tm.team == NUM_SPECTATOR)
1717 draw_beginBoldFont();
1718 vector rgb = Team_ColorRGB(tm.team);
1719 str = ftos(tm.(teamscores(ts_primary)));
1720 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1722 // team score on the left (default)
1723 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1727 // team score on the right
1728 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1730 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1732 // team size (if set to show on the side)
1733 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1735 // calculate the starting position for the whole team size info string
1736 str = sprintf("%d/%d", tm.team_size, team_size_total);
1737 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1739 // team size on the left
1740 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1744 // team size on the right
1745 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1747 str = sprintf("%d", tm.team_size);
1748 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1749 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1750 str = sprintf("/%d", team_size_total);
1751 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1755 // secondary score, e.g. keyhunt
1756 if(ts_primary != ts_secondary)
1758 str = ftos(tm.(teamscores(ts_secondary)));
1759 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1762 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1767 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1770 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1773 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1774 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1775 else if(panel_bg_color_team > 0)
1776 panel_bg_color = rgb * panel_bg_color_team;
1778 panel_bg_color = rgb;
1779 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1781 panel_bg_color = panel_bg_color_save;
1785 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1786 if(tm.team != NUM_SPECTATOR)
1789 // display it anyway
1790 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1793 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1794 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1796 if(ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || (autocvar_hud_panel_scoreboard_ctf_leaderboard && ISGAMETYPE(CTF) && STAT(CTF_SHOWLEADERBOARD))) {
1797 if(race_speedaward) {
1798 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);
1799 pos.y += 1.25 * hud_fontsize.y;
1801 if(race_speedaward_alltimebest) {
1802 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);
1803 pos.y += 1.25 * hud_fontsize.y;
1805 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1808 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1811 for(pl = players.sort_next; pl; pl = pl.sort_next)
1813 if(pl.team == NUM_SPECTATOR)
1815 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1816 if(tm.team == NUM_SPECTATOR)
1818 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1819 draw_beginBoldFont();
1820 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1822 pos.y += 1.25 * hud_fontsize.y;
1824 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1825 pos.y += 1.25 * hud_fontsize.y;
1832 // print information about respawn status
1833 float respawn_time = STAT(RESPAWN_TIME);
1837 if(respawn_time < 0)
1839 // a negative number means we are awaiting respawn, time value is still the same
1840 respawn_time *= -1; // remove mark now that we checked it
1842 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1843 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1845 str = sprintf(_("^1Respawning in ^3%s^1..."),
1846 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1847 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1849 count_seconds(ceil(respawn_time - time))
1853 else if(time < respawn_time)
1855 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1856 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1857 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1859 count_seconds(ceil(respawn_time - time))
1863 else if(time >= respawn_time)
1864 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1866 pos.y += 1.2 * hud_fontsize.y;
1867 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1870 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;