1 #include "scoreboard.qh"
3 #include <client/draw.qh>
4 #include <client/hud/panel/chat.qh>
5 #include <client/hud/panel/quickmenu.qh>
6 #include <client/hud/panel/racetimer.qh>
7 #include <client/hud/panel/weapons.qh>
8 #include <common/constants.qh>
9 #include <common/ent_cs.qh>
10 #include <common/mapinfo.qh>
11 #include <common/minigames/cl_minigames.qh>
12 #include <common/net_linked.qh>
13 #include <common/scores.qh>
14 #include <common/stats.qh>
15 #include <common/teams.qh>
16 #include <common/items/inventory.qh>
20 void Scoreboard_Draw_Export(int fh)
22 // allow saving cvars that aesthetically change the panel into hud skin files
23 HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
24 HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
25 HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
26 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
27 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
28 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
29 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
30 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
31 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
32 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
33 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_eliminated");
34 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
35 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
36 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
39 const int MAX_SBT_FIELDS = MAX_SCORE;
41 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
42 float sbt_field_size[MAX_SBT_FIELDS + 1];
43 string sbt_field_title[MAX_SBT_FIELDS + 1];
46 string autocvar_hud_fontsize;
47 string hud_fontsize_str;
52 float sbt_fg_alpha_self;
54 float sbt_highlight_alpha;
55 float sbt_highlight_alpha_self;
56 float sbt_highlight_alpha_eliminated;
58 // provide basic panel cvars to old clients
59 // TODO remove them after a future release (0.8.2+)
60 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
61 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
62 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
63 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
64 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
65 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
66 noref string autocvar_hud_panel_scoreboard_bg_border = "";
67 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
69 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
70 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
71 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
72 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
73 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
74 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
75 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
76 bool autocvar_hud_panel_scoreboard_table_highlight = true;
77 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
78 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
79 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
80 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
81 float autocvar_hud_panel_scoreboard_namesize = 15;
82 float autocvar_hud_panel_scoreboard_team_size_position = 0;
84 bool autocvar_hud_panel_scoreboard_accuracy = true;
85 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
86 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
87 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
88 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
90 bool autocvar_hud_panel_scoreboard_itemstats = true;
91 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
92 bool autocvar_hud_panel_scoreboard_itemstats_filter = true;
93 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
94 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
96 bool autocvar_hud_panel_scoreboard_dynamichud = false;
98 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
99 bool autocvar_hud_panel_scoreboard_others_showscore = true;
100 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
101 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
102 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
103 bool autocvar_hud_panel_scoreboard_playerid = false;
104 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
105 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
107 // mode 0: returns translated label
108 // mode 1: prints name and description of all the labels
109 string Label_getInfo(string label, int mode)
112 label = "bckills"; // first case in the switch
116 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
117 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
118 case "caps": if (!mode) return CTX(_("SCO^caps")); else LOG_HELP(strcat("^3", "caps", " ^7", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
119 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
120 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
121 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
122 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
123 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
124 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
125 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
126 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
127 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
128 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
129 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
130 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
131 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
132 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
133 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
134 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
135 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
136 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
137 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
138 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
139 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
140 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
141 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
142 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
143 case "pickups": if (!mode) return CTX(_("SCO^pickups")); else LOG_HELP(strcat("^3", "pickups", " ^7", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
144 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
145 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
146 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
147 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
148 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
149 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
150 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
151 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
152 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
153 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
154 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
155 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
156 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
157 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
158 default: return label;
163 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
164 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
166 void Scoreboard_InitScores()
170 ps_primary = ps_secondary = NULL;
171 ts_primary = ts_secondary = -1;
172 FOREACH(Scores, true, {
173 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
174 if(f == SFL_SORT_PRIO_PRIMARY)
176 if(f == SFL_SORT_PRIO_SECONDARY)
179 if(ps_secondary == NULL)
180 ps_secondary = ps_primary;
182 for(i = 0; i < MAX_TEAMSCORE; ++i)
184 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
185 if(f == SFL_SORT_PRIO_PRIMARY)
187 if(f == SFL_SORT_PRIO_SECONDARY)
190 if(ts_secondary == -1)
191 ts_secondary = ts_primary;
193 Cmd_Scoreboard_SetFields(0);
197 void Scoreboard_UpdatePlayerTeams()
201 for(pl = players.sort_next; pl; pl = pl.sort_next)
204 int Team = entcs_GetScoreTeam(pl.sv_entnum);
205 if(SetTeam(pl, Team))
208 Scoreboard_UpdatePlayerPos(pl);
212 pl = players.sort_next;
217 print(strcat("PNUM: ", ftos(num), "\n"));
222 int Scoreboard_CompareScore(int vl, int vr, int f)
224 TC(int, vl); TC(int, vr); TC(int, f);
225 if(f & SFL_ZERO_IS_WORST)
227 if(vl == 0 && vr != 0)
229 if(vl != 0 && vr == 0)
233 return IS_INCREASING(f);
235 return IS_DECREASING(f);
239 float Scoreboard_ComparePlayerScores(entity left, entity right)
242 vl = entcs_GetTeam(left.sv_entnum);
243 vr = entcs_GetTeam(right.sv_entnum);
255 if(vl == NUM_SPECTATOR)
257 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
259 if(!left.gotscores && right.gotscores)
264 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
268 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
272 FOREACH(Scores, true, {
273 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
274 if (r >= 0) return r;
277 if (left.sv_entnum < right.sv_entnum)
283 void Scoreboard_UpdatePlayerPos(entity player)
286 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
288 SORT_SWAP(player, ent);
290 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
292 SORT_SWAP(ent, player);
296 float Scoreboard_CompareTeamScores(entity left, entity right)
300 if(left.team == NUM_SPECTATOR)
302 if(right.team == NUM_SPECTATOR)
305 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
309 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
313 for(i = 0; i < MAX_TEAMSCORE; ++i)
315 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
320 if (left.team < right.team)
326 void Scoreboard_UpdateTeamPos(entity Team)
329 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
331 SORT_SWAP(Team, ent);
333 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
335 SORT_SWAP(ent, Team);
339 void Cmd_Scoreboard_Help()
341 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
342 LOG_HELP(_("Usage:"));
343 LOG_HELP("^2scoreboard_columns_set ^3default");
344 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
345 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
346 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
347 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
348 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
349 LOG_HELP(_("The following field names are recognized (case insensitive):"));
355 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
356 "of game types, then a slash, to make the field show up only in these\n"
357 "or in all but these game types. You can also specify 'all' as a\n"
358 "field to show all fields available for the current game mode."));
361 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
362 "include/exclude ALL teams/noteams game modes."));
365 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
366 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
367 "right of the vertical bar aligned to the right."));
368 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
369 "other gamemodes except DM."));
372 // NOTE: adding a gametype with ? to not warn for an optional field
373 // make sure it's excluded in a previous exclusive rule, if any
374 // otherwise the previous exclusive rule warns anyway
375 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
376 #define SCOREBOARD_DEFAULT_COLUMNS \
377 "ping pl fps name |" \
378 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
379 " -teams,lms/deaths +ft,tdm/deaths" \
381 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
382 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
383 " +tdm,ft,dom,ons,as/teamkills"\
384 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
385 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
386 " +lms/lives +lms/rank" \
387 " +kh/kckills +kh/losses +kh/caps" \
388 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
389 " +as/objectives +nb/faults +nb/goals" \
390 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
391 " +dom/ticks +dom/takes" \
392 " -lms,rc,cts,inv,nb/score"
394 void Cmd_Scoreboard_SetFields(int argc)
399 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
403 return; // do nothing, we don't know gametype and scores yet
405 // sbt_fields uses strunzone on the titles!
406 if(!sbt_field_title[0])
407 for(i = 0; i < MAX_SBT_FIELDS; ++i)
408 sbt_field_title[i] = strzone("(null)");
410 // TODO: re enable with gametype dependant cvars?
411 if(argc < 3) // no arguments provided
412 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
415 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
419 if(argv(2) == "default" || argv(2) == "expand_default")
421 if(argv(2) == "expand_default")
422 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
423 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
425 else if(argv(2) == "all")
427 string s = "ping pl name |"; // scores without a label
428 FOREACH(Scores, true, {
430 if(it != ps_secondary)
431 if(scores_label(it) != "")
432 s = strcat(s, " ", scores_label(it));
434 if(ps_secondary != ps_primary)
435 s = strcat(s, " ", scores_label(ps_secondary));
436 s = strcat(s, " ", scores_label(ps_primary));
437 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
444 hud_fontsize = HUD_GetFontsize("hud_fontsize");
446 for(i = 1; i < argc - 1; ++i)
449 bool nocomplain = false;
450 if(substring(str, 0, 1) == "?")
453 str = substring(str, 1, strlen(str) - 1);
456 slash = strstrofs(str, "/", 0);
459 pattern = substring(str, 0, slash);
460 str = substring(str, slash + 1, strlen(str) - (slash + 1));
462 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
466 str = strtolower(str);
467 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
468 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
473 // fields without a label (not networked)
474 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
475 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
476 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
477 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
478 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
479 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
480 default: // fields with a label
482 // map alternative labels
483 if (str == "damage") str = "dmg";
484 if (str == "damagetaken") str = "dmgtaken";
486 FOREACH(Scores, true, {
487 if (str == strtolower(scores_label(it))) {
489 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
498 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
499 if(!nocomplain && str != "fps") // server can disable the fps field
500 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
502 strfree(sbt_field_title[sbt_num_fields]);
503 sbt_field_size[sbt_num_fields] = 0;
507 sbt_field[sbt_num_fields] = j;
510 if(j == ps_secondary)
511 have_secondary = true;
516 if(sbt_num_fields >= MAX_SBT_FIELDS)
520 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
522 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
523 have_secondary = true;
524 if(ps_primary == ps_secondary)
525 have_secondary = true;
526 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
528 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
532 strfree(sbt_field_title[sbt_num_fields]);
533 for(i = sbt_num_fields; i > 0; --i)
535 sbt_field_title[i] = sbt_field_title[i-1];
536 sbt_field_size[i] = sbt_field_size[i-1];
537 sbt_field[i] = sbt_field[i-1];
539 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
540 sbt_field[0] = SP_NAME;
542 LOG_INFO("fixed missing field 'name'");
546 strfree(sbt_field_title[sbt_num_fields]);
547 for(i = sbt_num_fields; i > 1; --i)
549 sbt_field_title[i] = sbt_field_title[i-1];
550 sbt_field_size[i] = sbt_field_size[i-1];
551 sbt_field[i] = sbt_field[i-1];
553 sbt_field_title[1] = strzone("|");
554 sbt_field[1] = SP_SEPARATOR;
555 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
557 LOG_INFO("fixed missing field '|'");
560 else if(!have_separator)
562 strcpy(sbt_field_title[sbt_num_fields], "|");
563 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
564 sbt_field[sbt_num_fields] = SP_SEPARATOR;
566 LOG_INFO("fixed missing field '|'");
570 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
571 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
572 sbt_field[sbt_num_fields] = ps_secondary;
574 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
578 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
579 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
580 sbt_field[sbt_num_fields] = ps_primary;
582 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
586 sbt_field[sbt_num_fields] = SP_END;
589 string Scoreboard_AddPlayerId(string pl_name, entity pl)
591 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
592 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
593 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
597 vector sbt_field_rgb;
598 string sbt_field_icon0;
599 string sbt_field_icon1;
600 string sbt_field_icon2;
601 vector sbt_field_icon0_rgb;
602 vector sbt_field_icon1_rgb;
603 vector sbt_field_icon2_rgb;
604 string Scoreboard_GetName(entity pl)
606 if(ready_waiting && pl.ready)
608 sbt_field_icon0 = "gfx/scoreboard/player_ready";
612 int f = entcs_GetClientColors(pl.sv_entnum);
614 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
615 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
616 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
617 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
618 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
621 return entcs_GetName(pl.sv_entnum);
624 string Scoreboard_GetField(entity pl, PlayerScoreField field)
626 float tmp, num, denom;
629 sbt_field_rgb = '1 1 1';
630 sbt_field_icon0 = "";
631 sbt_field_icon1 = "";
632 sbt_field_icon2 = "";
633 sbt_field_icon0_rgb = '1 1 1';
634 sbt_field_icon1_rgb = '1 1 1';
635 sbt_field_icon2_rgb = '1 1 1';
640 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
641 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
645 tmp = max(0, min(220, f-80)) / 220;
646 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
652 f = pl.ping_packetloss;
653 tmp = pl.ping_movementloss;
654 if(f == 0 && tmp == 0)
656 str = ftos(ceil(f * 100));
658 str = strcat(str, "~", ftos(ceil(tmp * 100)));
659 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
660 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
664 str = Scoreboard_GetName(pl);
665 if (autocvar_hud_panel_scoreboard_playerid)
666 str = Scoreboard_AddPlayerId(str, pl);
670 f = pl.(scores(SP_KILLS));
671 f -= pl.(scores(SP_SUICIDES));
675 num = pl.(scores(SP_KILLS));
676 denom = pl.(scores(SP_DEATHS));
679 sbt_field_rgb = '0 1 0';
680 str = sprintf("%d", num);
681 } else if(num <= 0) {
682 sbt_field_rgb = '1 0 0';
683 str = sprintf("%.1f", num/denom);
685 str = sprintf("%.1f", num/denom);
689 f = pl.(scores(SP_KILLS));
690 f -= pl.(scores(SP_DEATHS));
693 sbt_field_rgb = '0 1 0';
695 sbt_field_rgb = '1 1 1';
697 sbt_field_rgb = '1 0 0';
703 float elo = pl.(scores(SP_ELO));
705 case -1: return "...";
706 case -2: return _("N/A");
707 default: return ftos(elo);
713 float fps = pl.(scores(SP_FPS));
716 sbt_field_rgb = '1 1 1';
717 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
719 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
720 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
724 case SP_DMG: case SP_DMGTAKEN:
725 return sprintf("%.1f k", pl.(scores(field)) / 1000);
727 default: case SP_SCORE:
728 tmp = pl.(scores(field));
729 f = scores_flags(field);
730 if(field == ps_primary)
731 sbt_field_rgb = '1 1 0';
732 else if(field == ps_secondary)
733 sbt_field_rgb = '0 1 1';
735 sbt_field_rgb = '1 1 1';
736 return ScoreString(f, tmp);
741 float sbt_fixcolumnwidth_len;
742 float sbt_fixcolumnwidth_iconlen;
743 float sbt_fixcolumnwidth_marginlen;
745 string Scoreboard_FixColumnWidth(int i, string str)
751 sbt_fixcolumnwidth_iconlen = 0;
753 if(sbt_field_icon0 != "")
755 sz = draw_getimagesize(sbt_field_icon0);
757 if(sbt_fixcolumnwidth_iconlen < f)
758 sbt_fixcolumnwidth_iconlen = f;
761 if(sbt_field_icon1 != "")
763 sz = draw_getimagesize(sbt_field_icon1);
765 if(sbt_fixcolumnwidth_iconlen < f)
766 sbt_fixcolumnwidth_iconlen = f;
769 if(sbt_field_icon2 != "")
771 sz = draw_getimagesize(sbt_field_icon2);
773 if(sbt_fixcolumnwidth_iconlen < f)
774 sbt_fixcolumnwidth_iconlen = f;
777 if(sbt_fixcolumnwidth_iconlen != 0)
779 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
780 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
783 sbt_fixcolumnwidth_marginlen = 0;
785 if(sbt_field[i] == SP_NAME) // name gets all remaining space
788 float remaining_space = 0;
789 for(j = 0; j < sbt_num_fields; ++j)
791 if (sbt_field[i] != SP_SEPARATOR)
792 remaining_space += sbt_field_size[j] + hud_fontsize.x;
793 sbt_field_size[i] = panel_size.x - remaining_space;
795 if (sbt_fixcolumnwidth_iconlen != 0)
796 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
797 float namesize = panel_size.x - remaining_space;
798 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
799 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
801 max_namesize = vid_conwidth - remaining_space;
804 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
806 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
807 if(sbt_field_size[i] < f)
808 sbt_field_size[i] = f;
813 void Scoreboard_initFieldSizes()
815 for(int i = 0; i < sbt_num_fields; ++i)
817 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
818 Scoreboard_FixColumnWidth(i, "");
822 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
825 vector column_dim = eY * panel_size.y;
827 column_dim.y -= 1.25 * hud_fontsize.y;
828 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
829 pos.x += hud_fontsize.x * 0.5;
830 for(i = 0; i < sbt_num_fields; ++i)
832 if(sbt_field[i] == SP_SEPARATOR)
834 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
837 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
838 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
839 pos.x += column_dim.x;
841 if(sbt_field[i] == SP_SEPARATOR)
843 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
844 for(i = sbt_num_fields - 1; i > 0; --i)
846 if(sbt_field[i] == SP_SEPARATOR)
849 pos.x -= sbt_field_size[i];
854 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
855 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
858 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
859 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
860 pos.x -= hud_fontsize.x;
865 pos.y += 1.25 * hud_fontsize.y;
869 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
871 TC(bool, is_self); TC(int, pl_number);
873 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
875 vector h_pos = item_pos;
876 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
877 // alternated rows highlighting
879 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
880 else if((sbt_highlight) && (!(pl_number % 2)))
881 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
883 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
885 vector pos = item_pos;
886 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
888 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
890 pos.x += hud_fontsize.x * 0.5;
891 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
892 vector tmp = '0 0 0';
894 PlayerScoreField field;
895 for(i = 0; i < sbt_num_fields; ++i)
897 field = sbt_field[i];
898 if(field == SP_SEPARATOR)
901 if(is_spec && field != SP_NAME && field != SP_PING) {
902 pos.x += sbt_field_size[i] + hud_fontsize.x;
905 str = Scoreboard_GetField(pl, field);
906 str = Scoreboard_FixColumnWidth(i, str);
908 pos.x += sbt_field_size[i] + hud_fontsize.x;
910 if(field == SP_NAME) {
911 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
912 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
914 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
915 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
918 tmp.x = sbt_field_size[i] + hud_fontsize.x;
919 if(sbt_field_icon0 != "")
920 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
921 if(sbt_field_icon1 != "")
922 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
923 if(sbt_field_icon2 != "")
924 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
927 if(sbt_field[i] == SP_SEPARATOR)
929 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
930 for(i = sbt_num_fields-1; i > 0; --i)
932 field = sbt_field[i];
933 if(field == SP_SEPARATOR)
936 if(is_spec && field != SP_NAME && field != SP_PING) {
937 pos.x -= sbt_field_size[i] + hud_fontsize.x;
941 str = Scoreboard_GetField(pl, field);
942 str = Scoreboard_FixColumnWidth(i, str);
944 if(field == SP_NAME) {
945 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
946 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
948 tmp.x = sbt_fixcolumnwidth_len;
949 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
952 tmp.x = sbt_field_size[i];
953 if(sbt_field_icon0 != "")
954 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
955 if(sbt_field_icon1 != "")
956 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
957 if(sbt_field_icon2 != "")
958 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
959 pos.x -= sbt_field_size[i] + hud_fontsize.x;
964 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
967 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
970 vector h_pos = item_pos;
971 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
973 bool complete = (this_team == NUM_SPECTATOR);
976 if((sbt_highlight) && (!(pl_number % 2)))
977 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
979 vector pos = item_pos;
980 pos.x += hud_fontsize.x * 0.5;
981 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
983 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
985 width_limit -= stringwidth("...", false, hud_fontsize);
986 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
987 static float max_name_width = 0;
990 float min_fieldsize = 0;
991 float fieldpadding = hud_fontsize.x * 0.25;
992 if(this_team == NUM_SPECTATOR)
994 if(autocvar_hud_panel_scoreboard_spectators_showping)
995 min_fieldsize = stringwidth("999", false, hud_fontsize);
997 else if(autocvar_hud_panel_scoreboard_others_showscore)
998 min_fieldsize = stringwidth("99", false, hud_fontsize);
999 for(i = 0; pl; pl = pl.sort_next)
1001 if(pl.team != this_team)
1003 if(pl == ignored_pl)
1007 if(this_team == NUM_SPECTATOR)
1009 if(autocvar_hud_panel_scoreboard_spectators_showping)
1010 field = Scoreboard_GetField(pl, SP_PING);
1012 else if(autocvar_hud_panel_scoreboard_others_showscore)
1013 field = Scoreboard_GetField(pl, SP_SCORE);
1015 string str = entcs_GetName(pl.sv_entnum);
1016 if (autocvar_hud_panel_scoreboard_playerid)
1017 str = Scoreboard_AddPlayerId(str, pl);
1018 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1019 float column_width = stringwidth(str, true, hud_fontsize);
1020 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1022 if(column_width > max_name_width)
1023 max_name_width = column_width;
1024 column_width = max_name_width;
1028 fieldsize = stringwidth(field, false, hud_fontsize);
1029 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1032 if(pos.x + column_width > width_limit)
1037 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1042 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1043 pos.y += hud_fontsize.y * 1.25;
1047 vector name_pos = pos;
1048 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1049 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1050 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1053 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1054 h_size.y = hud_fontsize.y;
1055 vector field_pos = pos;
1056 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1057 field_pos.x += column_width - h_size.x;
1059 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1060 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1061 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1065 h_size.x = column_width + hud_fontsize.x * 0.25;
1066 h_size.y = hud_fontsize.y;
1067 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1069 pos.x += column_width;
1070 pos.x += hud_fontsize.x;
1072 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1075 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1077 int max_players = 999;
1078 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1080 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1083 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1084 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1085 height /= team_count;
1088 height -= panel_bg_padding * 2; // - padding
1089 max_players = floor(height / (hud_fontsize.y * 1.25));
1090 if(max_players <= 1)
1092 if(max_players == tm.team_size)
1097 entity me = playerslots[current_player];
1099 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1100 panel_size.y += panel_bg_padding * 2;
1103 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1104 if(panel.current_panel_bg != "0")
1105 end_pos.y += panel_bg_border * 2;
1107 if(panel_bg_padding)
1109 panel_pos += '1 1 0' * panel_bg_padding;
1110 panel_size -= '2 2 0' * panel_bg_padding;
1114 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1118 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1120 pos.y += 1.25 * hud_fontsize.y;
1123 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1125 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1128 // print header row and highlight columns
1129 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1131 // fill the table and draw the rows
1132 bool is_self = false;
1133 bool self_shown = false;
1135 for(pl = players.sort_next; pl; pl = pl.sort_next)
1137 if(pl.team != tm.team)
1139 if(i == max_players - 2 && pl != me)
1141 if(!self_shown && me.team == tm.team)
1143 Scoreboard_DrawItem(pos, rgb, me, true, i);
1145 pos.y += 1.25 * hud_fontsize.y;
1149 if(i >= max_players - 1)
1151 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1154 is_self = (pl.sv_entnum == current_player);
1155 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1158 pos.y += 1.25 * hud_fontsize.y;
1162 panel_size.x += panel_bg_padding * 2; // restore initial width
1166 bool Scoreboard_WouldDraw()
1168 if (MUTATOR_CALLHOOK(DrawScoreboard))
1170 else if (QuickMenu_IsOpened())
1172 else if (HUD_Radar_Clickable())
1174 else if (scoreboard_showscores)
1176 else if (intermission == 1)
1178 else if (intermission == 2)
1180 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1181 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1185 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1190 float average_accuracy;
1191 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1193 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1195 WepSet weapons_stat = WepSet_GetFromStat();
1196 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1197 int disownedcnt = 0;
1199 FOREACH(Weapons, it != WEP_Null, {
1200 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1202 WepSet set = it.m_wepset;
1203 if(it.spawnflags & WEP_TYPE_OTHER)
1208 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1210 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1217 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1218 if (weapon_cnt <= 0) return pos;
1221 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1223 int columnns = ceil(weapon_cnt / rows);
1225 float weapon_height = 29;
1226 float height = hud_fontsize.y + weapon_height;
1228 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);
1229 pos.y += 1.25 * hud_fontsize.y;
1230 if(panel.current_panel_bg != "0")
1231 pos.y += panel_bg_border;
1234 panel_size.y = height * rows;
1235 panel_size.y += panel_bg_padding * 2;
1237 float panel_bg_alpha_save = panel_bg_alpha;
1238 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1240 panel_bg_alpha = panel_bg_alpha_save;
1242 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1243 if(panel.current_panel_bg != "0")
1244 end_pos.y += panel_bg_border * 2;
1246 if(panel_bg_padding)
1248 panel_pos += '1 1 0' * panel_bg_padding;
1249 panel_size -= '2 2 0' * panel_bg_padding;
1253 vector tmp = panel_size;
1255 float weapon_width = tmp.x / columnns / rows;
1258 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1262 // column highlighting
1263 for (int i = 0; i < columnns; ++i)
1265 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);
1268 for (int i = 0; i < rows; ++i)
1269 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1272 average_accuracy = 0;
1273 int weapons_with_stats = 0;
1275 pos.x += weapon_width / 2;
1277 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1280 Accuracy_LoadColors();
1282 float oldposx = pos.x;
1286 FOREACH(Weapons, it != WEP_Null, {
1287 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1289 WepSet set = it.m_wepset;
1290 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1292 if (it.spawnflags & WEP_TYPE_OTHER)
1296 if (weapon_stats >= 0)
1297 weapon_alpha = sbt_fg_alpha;
1299 weapon_alpha = 0.2 * sbt_fg_alpha;
1302 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1304 if (weapon_stats >= 0) {
1305 weapons_with_stats += 1;
1306 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1309 s = sprintf("%d%%", weapon_stats * 100);
1312 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1314 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1315 rgb = Accuracy_GetColor(weapon_stats);
1317 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1319 tmpos.x += weapon_width * rows;
1320 pos.x += weapon_width * rows;
1321 if (rows == 2 && column == columnns - 1) {
1329 if (weapons_with_stats)
1330 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1332 panel_size.x += panel_bg_padding * 2; // restore initial width
1337 .bool uninteresting;
1338 STATIC_INIT(default_order_items_label)
1340 IL_EACH(default_order_items, true, {
1341 if(!(it.instanceOfPowerup
1342 || it == ITEM_HealthMega || it == ITEM_HealthBig
1343 || it == ITEM_ArmorMega || it == ITEM_ArmorBig
1346 it.uninteresting = true;
1351 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1353 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1355 int disowned_cnt = 0;
1356 int uninteresting_cnt = 0;
1357 IL_EACH(default_order_items, true, {
1358 int q = g_inventory.inv_items[it.m_id];
1359 //q = 1; // debug: display all items
1360 if (autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting)
1361 ++uninteresting_cnt;
1365 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1366 int n = items_cnt - disowned_cnt;
1367 if (n <= 0) return pos;
1369 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1370 int columnns = max(6, ceil(n / rows));
1373 float fontsize = height * 1/3;
1374 float item_height = height * 2/3;
1376 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1377 pos.y += 1.25 * hud_fontsize.y;
1378 if(panel.current_panel_bg != "0")
1379 pos.y += panel_bg_border;
1382 panel_size.y = height * rows;
1383 panel_size.y += panel_bg_padding * 2;
1385 float panel_bg_alpha_save = panel_bg_alpha;
1386 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1388 panel_bg_alpha = panel_bg_alpha_save;
1390 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1391 if(panel.current_panel_bg != "0")
1392 end_pos.y += panel_bg_border * 2;
1394 if(panel_bg_padding)
1396 panel_pos += '1 1 0' * panel_bg_padding;
1397 panel_size -= '2 2 0' * panel_bg_padding;
1401 vector tmp = panel_size;
1403 float item_width = tmp.x / columnns / rows;
1406 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1410 // column highlighting
1411 for (int i = 0; i < columnns; ++i)
1413 drawfill(pos + '1 0 0' * item_width * rows * i, '0 1 0' * height * rows + '1 0 0' * item_width * rows, '0 0 0', sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1416 for (int i = 0; i < rows; ++i)
1417 drawfill(pos + '0 1 0' * item_height + '0 1 0' * height * i, '1 0 0' * panel_size.x + '0 1 0' * fontsize, rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1421 pos.x += item_width / 2;
1423 float oldposx = pos.x;
1427 IL_EACH(default_order_items, !(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting), {
1428 int n = g_inventory.inv_items[it.m_id];
1429 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1430 if (n <= 0) continue;
1431 drawpic_aspect_skin(tmpos, it.m_icon, '1 0 0' * item_width + '0 1 0' * item_height, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1433 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1434 drawstring(tmpos + '1 0 0' * padding + '0 1 0' * item_height, s, '1 1 0' * fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1435 tmpos.x += item_width * rows;
1436 pos.x += item_width * rows;
1437 if (rows == 2 && column == columnns - 1) {
1445 panel_size.x += panel_bg_padding * 2; // restore initial width
1450 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1452 pos.x += hud_fontsize.x * 0.25;
1453 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1454 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1455 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1457 pos.y += hud_fontsize.y;
1462 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1463 float stat_secrets_found, stat_secrets_total;
1464 float stat_monsters_killed, stat_monsters_total;
1468 // get monster stats
1469 stat_monsters_killed = STAT(MONSTERS_KILLED);
1470 stat_monsters_total = STAT(MONSTERS_TOTAL);
1472 // get secrets stats
1473 stat_secrets_found = STAT(SECRETS_FOUND);
1474 stat_secrets_total = STAT(SECRETS_TOTAL);
1476 // get number of rows
1477 if(stat_secrets_total)
1479 if(stat_monsters_total)
1482 // if no rows, return
1486 // draw table header
1487 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1488 pos.y += 1.25 * hud_fontsize.y;
1489 if(panel.current_panel_bg != "0")
1490 pos.y += panel_bg_border;
1493 panel_size.y = hud_fontsize.y * rows;
1494 panel_size.y += panel_bg_padding * 2;
1497 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1498 if(panel.current_panel_bg != "0")
1499 end_pos.y += panel_bg_border * 2;
1501 if(panel_bg_padding)
1503 panel_pos += '1 1 0' * panel_bg_padding;
1504 panel_size -= '2 2 0' * panel_bg_padding;
1508 vector tmp = panel_size;
1511 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1514 if(stat_monsters_total)
1516 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1517 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1521 if(stat_secrets_total)
1523 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1524 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1527 panel_size.x += panel_bg_padding * 2; // restore initial width
1532 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1535 RANKINGS_RECEIVED_CNT = 0;
1536 for (i=RANKINGS_CNT-1; i>=0; --i)
1538 ++RANKINGS_RECEIVED_CNT;
1540 if (RANKINGS_RECEIVED_CNT == 0)
1543 vector hl_rgb = rgb + '0.5 0.5 0.5';
1545 pos.y += hud_fontsize.y;
1546 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1547 pos.y += 1.25 * hud_fontsize.y;
1548 if(panel.current_panel_bg != "0")
1549 pos.y += panel_bg_border;
1554 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1556 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1561 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1563 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1567 float ranksize = 3 * hud_fontsize.x;
1568 float timesize = 5 * hud_fontsize.x;
1569 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1570 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1571 columns = min(columns, RANKINGS_RECEIVED_CNT);
1573 // expand name column to fill the entire row
1574 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1575 namesize += available_space;
1576 columnsize.x += available_space;
1578 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1579 panel_size.y += panel_bg_padding * 2;
1583 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1584 if(panel.current_panel_bg != "0")
1585 end_pos.y += panel_bg_border * 2;
1587 if(panel_bg_padding)
1589 panel_pos += '1 1 0' * panel_bg_padding;
1590 panel_size -= '2 2 0' * panel_bg_padding;
1596 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1598 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1600 int column = 0, j = 0;
1601 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1602 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1609 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1610 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1611 else if(!((j + column) & 1) && sbt_highlight)
1612 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1614 str = count_ordinal(i+1);
1615 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1616 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1617 str = ColorTranslateRGB(grecordholder[i]);
1619 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1620 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1622 pos.y += 1.25 * hud_fontsize.y;
1624 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1628 pos.x += panel_size.x / columns;
1629 pos.y = panel_pos.y;
1632 strfree(zoned_name_self);
1634 panel_size.x += panel_bg_padding * 2; // restore initial width
1638 float scoreboard_time;
1639 bool have_weapon_stats;
1640 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1642 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1644 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1647 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1648 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1654 if (!have_weapon_stats)
1656 FOREACH(Weapons, it != WEP_Null, {
1657 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1658 if (weapon_stats >= 0)
1660 have_weapon_stats = true;
1664 if (!have_weapon_stats)
1671 bool have_item_stats;
1672 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1674 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1676 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1679 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1680 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1686 if (!have_item_stats)
1688 IL_EACH(default_order_items, true, {
1689 if (!(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting))
1691 int q = g_inventory.inv_items[it.m_id];
1692 //q = 1; // debug: display all items
1695 have_item_stats = true;
1700 if (!have_item_stats)
1707 void Scoreboard_Draw()
1709 if(!autocvar__hud_configure)
1711 if(!hud_draw_maximized) return;
1713 // frametime checks allow to toggle the scoreboard even when the game is paused
1714 if(scoreboard_active) {
1715 if (scoreboard_fade_alpha == 0)
1716 scoreboard_time = time;
1717 if(hud_configure_menu_open == 1)
1718 scoreboard_fade_alpha = 1;
1719 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1720 if (scoreboard_fadeinspeed && frametime)
1721 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1723 scoreboard_fade_alpha = 1;
1724 if(hud_fontsize_str != autocvar_hud_fontsize)
1726 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1727 Scoreboard_initFieldSizes();
1728 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1732 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1733 if (scoreboard_fadeoutspeed && frametime)
1734 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1736 scoreboard_fade_alpha = 0;
1739 if (!scoreboard_fade_alpha)
1741 scoreboard_acc_fade_alpha = 0;
1742 scoreboard_itemstats_fade_alpha = 0;
1747 scoreboard_fade_alpha = 0;
1749 if (autocvar_hud_panel_scoreboard_dynamichud)
1752 HUD_Scale_Disable();
1754 if(scoreboard_fade_alpha <= 0)
1756 panel_fade_alpha *= scoreboard_fade_alpha;
1757 HUD_Panel_LoadCvars();
1759 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1760 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1761 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1762 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1763 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1764 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1765 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1767 // don't overlap with con_notify
1768 if(!autocvar__hud_configure)
1769 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1771 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1772 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1773 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1774 panel_size.x = fixed_scoreboard_width;
1776 Scoreboard_UpdatePlayerTeams();
1778 float initial_pos_y = panel_pos.y;
1779 vector pos = panel_pos;
1784 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1786 // Begin of Game Info Section
1787 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1788 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1790 // Game Info: Game Type
1791 str = MapInfo_Type_ToText(gametype);
1792 draw_beginBoldFont();
1793 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);
1796 // Game Info: Game Detail
1797 float tl = STAT(TIMELIMIT);
1798 float fl = STAT(FRAGLIMIT);
1799 float ll = STAT(LEADLIMIT);
1800 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1803 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1804 if(!gametype.m_hidelimits)
1809 str = strcat(str, "^7 / "); // delimiter
1812 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1813 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1814 (teamscores_label(ts_primary) == "fastest") ? "" :
1815 TranslateScoresLabel(teamscores_label(ts_primary))));
1819 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1820 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1821 (scores_label(ps_primary) == "fastest") ? "" :
1822 TranslateScoresLabel(scores_label(ps_primary))));
1827 if(tl > 0 || fl > 0)
1830 if (ll_and_fl && fl > 0)
1831 str = strcat(str, "^7 & ");
1833 str = strcat(str, "^7 / ");
1838 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1839 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1840 (teamscores_label(ts_primary) == "fastest") ? "" :
1841 TranslateScoresLabel(teamscores_label(ts_primary))));
1845 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1846 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1847 (scores_label(ps_primary) == "fastest") ? "" :
1848 TranslateScoresLabel(scores_label(ps_primary))));
1853 pos.y += sb_gameinfo_type_fontsize.y;
1854 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
1856 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1857 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1858 // End of Game Info Section
1860 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1861 if(panel.current_panel_bg != "0")
1862 pos.y += panel_bg_border;
1864 // Draw the scoreboard
1865 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1868 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1872 vector panel_bg_color_save = panel_bg_color;
1873 vector team_score_baseoffset;
1874 vector team_size_baseoffset;
1875 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1877 // put team score to the left of scoreboard (and team size to the right)
1878 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1879 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1880 if(panel.current_panel_bg != "0")
1882 team_score_baseoffset.x -= panel_bg_border;
1883 team_size_baseoffset.x += panel_bg_border;
1888 // put team score to the right of scoreboard (and team size to the left)
1889 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1890 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1891 if(panel.current_panel_bg != "0")
1893 team_score_baseoffset.x += panel_bg_border;
1894 team_size_baseoffset.x -= panel_bg_border;
1898 int team_size_total = 0;
1899 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1901 // calculate team size total (sum of all team sizes)
1902 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1903 if(tm.team != NUM_SPECTATOR)
1904 team_size_total += tm.team_size;
1907 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1909 if(tm.team == NUM_SPECTATOR)
1914 draw_beginBoldFont();
1915 vector rgb = Team_ColorRGB(tm.team);
1916 str = ftos(tm.(teamscores(ts_primary)));
1917 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1919 // team score on the left (default)
1920 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1924 // team score on the right
1925 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1927 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1929 // team size (if set to show on the side)
1930 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1932 // calculate the starting position for the whole team size info string
1933 str = sprintf("%d/%d", tm.team_size, team_size_total);
1934 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1936 // team size on the left
1937 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1941 // team size on the right
1942 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1944 str = sprintf("%d", tm.team_size);
1945 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1946 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1947 str = sprintf("/%d", team_size_total);
1948 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1952 // secondary score, e.g. keyhunt
1953 if(ts_primary != ts_secondary)
1955 str = ftos(tm.(teamscores(ts_secondary)));
1956 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1959 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1964 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1967 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1970 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1971 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1972 else if(panel_bg_color_team > 0)
1973 panel_bg_color = rgb * panel_bg_color_team;
1975 panel_bg_color = rgb;
1976 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1978 panel_bg_color = panel_bg_color_save;
1982 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1983 if(tm.team != NUM_SPECTATOR)
1986 // display it anyway
1987 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1990 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1991 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1992 if (Scoreboard_ItemStats_WouldDraw(pos.y))
1993 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
1995 if(MUTATOR_CALLHOOK(ShowRankings)) {
1996 string ranktitle = M_ARGV(0, string);
1997 if(race_speedaward) {
1998 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);
1999 pos.y += 1.25 * hud_fontsize.y;
2001 if(race_speedaward_alltimebest) {
2002 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);
2003 pos.y += 1.25 * hud_fontsize.y;
2005 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2008 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2011 for(pl = players.sort_next; pl; pl = pl.sort_next)
2013 if(pl.team == NUM_SPECTATOR)
2015 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2016 if(tm.team == NUM_SPECTATOR)
2018 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2019 draw_beginBoldFont();
2020 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2022 pos.y += 1.25 * hud_fontsize.y;
2024 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2025 pos.y += 1.25 * hud_fontsize.y;
2032 // print information about respawn status
2033 float respawn_time = STAT(RESPAWN_TIME);
2037 if(respawn_time < 0)
2039 // a negative number means we are awaiting respawn, time value is still the same
2040 respawn_time *= -1; // remove mark now that we checked it
2042 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2043 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2045 str = sprintf(_("^1Respawning in ^3%s^1..."),
2046 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2047 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2049 count_seconds(ceil(respawn_time - time))
2053 else if(time < respawn_time)
2055 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2056 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2057 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2059 count_seconds(ceil(respawn_time - time))
2063 else if(time >= respawn_time)
2064 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2066 pos.y += 1.2 * hud_fontsize.y;
2067 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2070 pos.y += 2 * hud_fontsize.y;
2071 if (scoreboard_fade_alpha < 1)
2072 scoreboard_bottom = initial_pos_y + (pos.y - initial_pos_y) * scoreboard_fade_alpha;
2073 else if (pos.y != scoreboard_bottom)
2075 if (pos.y > scoreboard_bottom)
2076 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - initial_pos_y));
2078 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - initial_pos_y));