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>
16 #include <common/items/inventory.qh>
20 const int MAX_SBT_FIELDS = MAX_SCORE;
22 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
23 float sbt_field_size[MAX_SBT_FIELDS + 1];
24 string sbt_field_title[MAX_SBT_FIELDS + 1];
27 string autocvar_hud_fontsize;
28 string hud_fontsize_str;
33 float sbt_fg_alpha_self;
35 float sbt_highlight_alpha;
36 float sbt_highlight_alpha_self;
38 // provide basic panel cvars to old clients
39 // TODO remove them after a future release (0.8.2+)
40 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
41 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
42 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
43 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
44 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
45 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
46 noref string autocvar_hud_panel_scoreboard_bg_border = "";
47 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
49 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
50 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
51 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
52 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
53 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
54 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
55 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
56 bool autocvar_hud_panel_scoreboard_table_highlight = true;
57 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
58 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
59 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
60 float autocvar_hud_panel_scoreboard_namesize = 15;
61 float autocvar_hud_panel_scoreboard_team_size_position = 0;
63 bool autocvar_hud_panel_scoreboard_accuracy = true;
64 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
65 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
66 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
67 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
69 bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
71 bool autocvar_hud_panel_scoreboard_dynamichud = false;
73 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
74 bool autocvar_hud_panel_scoreboard_others_showscore = true;
75 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
76 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
77 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
79 // mode 0: returns translated label
80 // mode 1: prints name and description of all the labels
81 string Label_getInfo(string label, int mode)
84 label = "bckills"; // first case in the switch
88 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_INFO(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
89 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_INFO(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
90 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")));
91 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_INFO(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
92 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_INFO(strcat("^3", "deaths", " ^7", _("Number of deaths")));
93 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_INFO(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
94 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_INFO(strcat("^3", "dmg", " ^7", _("The total damage done")));
95 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_INFO(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
96 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_INFO(strcat("^3", "drops", " ^7", _("Number of flag drops")));
97 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_INFO(strcat("^3", "elo", " ^7", _("Player ELO")));
98 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_INFO(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
99 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_INFO(strcat("^3", "faults", " ^7", _("Number of faults committed")));
100 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_INFO(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
101 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_INFO(strcat("^3", "fps", " ^7", _("FPS")));
102 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_INFO(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
103 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_INFO(strcat("^3", "goals", " ^7", _("Number of goals scored")));
104 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_INFO(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
105 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_INFO(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
106 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_INFO(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
107 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_INFO(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
108 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_INFO(strcat("^3", "kills", " ^7", _("Number of kills")));
109 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_INFO(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
110 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_INFO(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
111 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_INFO(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
112 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_INFO(strcat("^3", "name", " ^7", _("Player name")));
113 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_INFO(strcat("^3", "nick", " ^7", _("Player name")));
114 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_INFO(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
115 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")));
116 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_INFO(strcat("^3", "ping", " ^7", _("Ping time")));
117 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_INFO(strcat("^3", "pl", " ^7", _("Packet loss")));
118 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_INFO(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
119 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_INFO(strcat("^3", "rank", " ^7", _("Player rank")));
120 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_INFO(strcat("^3", "returns", " ^7", _("Number of flag returns")));
121 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_INFO(strcat("^3", "revivals", " ^7", _("Number of revivals")));
122 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_INFO(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
123 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_INFO(strcat("^3", "score", " ^7", _("Total score")));
124 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_INFO(strcat("^3", "suicides", " ^7", _("Number of suicides")));
125 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_INFO(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
126 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_INFO(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
127 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_INFO(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
128 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_INFO(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
129 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_INFO(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
130 default: return label;
135 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
136 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
138 void Scoreboard_InitScores()
142 ps_primary = ps_secondary = NULL;
143 ts_primary = ts_secondary = -1;
144 FOREACH(Scores, true, {
145 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
146 if(f == SFL_SORT_PRIO_PRIMARY)
148 if(f == SFL_SORT_PRIO_SECONDARY)
151 if(ps_secondary == NULL)
152 ps_secondary = ps_primary;
154 for(i = 0; i < MAX_TEAMSCORE; ++i)
156 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
157 if(f == SFL_SORT_PRIO_PRIMARY)
159 if(f == SFL_SORT_PRIO_SECONDARY)
162 if(ts_secondary == -1)
163 ts_secondary = ts_primary;
165 Cmd_Scoreboard_SetFields(0);
169 void Scoreboard_UpdatePlayerTeams()
173 for(pl = players.sort_next; pl; pl = pl.sort_next)
176 int Team = entcs_GetScoreTeam(pl.sv_entnum);
177 if(SetTeam(pl, Team))
180 Scoreboard_UpdatePlayerPos(pl);
184 pl = players.sort_next;
189 print(strcat("PNUM: ", ftos(num), "\n"));
194 int Scoreboard_CompareScore(int vl, int vr, int f)
196 TC(int, vl); TC(int, vr); TC(int, f);
197 if(f & SFL_ZERO_IS_WORST)
199 if(vl == 0 && vr != 0)
201 if(vl != 0 && vr == 0)
205 return IS_INCREASING(f);
207 return IS_DECREASING(f);
211 float Scoreboard_ComparePlayerScores(entity left, entity right)
214 vl = entcs_GetTeam(left.sv_entnum);
215 vr = entcs_GetTeam(right.sv_entnum);
227 if(vl == NUM_SPECTATOR)
229 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
231 if(!left.gotscores && right.gotscores)
236 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
240 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
244 FOREACH(Scores, true, {
245 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
246 if (r >= 0) return r;
249 if (left.sv_entnum < right.sv_entnum)
255 void Scoreboard_UpdatePlayerPos(entity player)
258 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
260 SORT_SWAP(player, ent);
262 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
264 SORT_SWAP(ent, player);
268 float Scoreboard_CompareTeamScores(entity left, entity right)
272 if(left.team == NUM_SPECTATOR)
274 if(right.team == NUM_SPECTATOR)
277 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
281 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
285 for(i = 0; i < MAX_TEAMSCORE; ++i)
287 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
292 if (left.team < right.team)
298 void Scoreboard_UpdateTeamPos(entity Team)
301 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
303 SORT_SWAP(Team, ent);
305 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
307 SORT_SWAP(ent, Team);
311 void Cmd_Scoreboard_Help()
313 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
314 LOG_INFO(_("Usage:"));
315 LOG_INFO("^2scoreboard_columns_set ^3default");
316 LOG_INFO(_("^2scoreboard_columns_set ^3field1 field2 ..."));
317 LOG_INFO(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
318 LOG_INFO(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
319 LOG_INFO(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
320 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields."));
321 LOG_INFO(_("The following field names are recognized (case insensitive):"));
327 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
328 "of game types, then a slash, to make the field show up only in these\n"
329 "or in all but these game types. You can also specify 'all' as a\n"
330 "field to show all fields available for the current game mode."));
333 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
334 "include/exclude ALL teams/noteams game modes."));
337 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
338 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
339 "right of the vertical bar aligned to the right."));
340 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
341 "other gamemodes except DM."));
344 // NOTE: adding a gametype with ? to not warn for an optional field
345 // make sure it's excluded in a previous exclusive rule, if any
346 // otherwise the previous exclusive rule warns anyway
347 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
348 #define SCOREBOARD_DEFAULT_COLUMNS \
349 "ping pl fps name |" \
350 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
351 " -teams,lms/deaths +ft,tdm/deaths" \
353 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
354 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
355 " +tdm,ft,dom,ons,as/teamkills"\
356 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
357 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
358 " +lms/lives +lms/rank" \
359 " +kh/kckills +kh/losses +kh/caps" \
360 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
361 " +as/objectives +nb/faults +nb/goals" \
362 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
363 " +dom/ticks +dom/takes" \
364 " -lms,rc,cts,inv,nb/score"
366 void Cmd_Scoreboard_SetFields(int argc)
371 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
375 return; // do nothing, we don't know gametype and scores yet
377 // sbt_fields uses strunzone on the titles!
378 if(!sbt_field_title[0])
379 for(i = 0; i < MAX_SBT_FIELDS; ++i)
380 sbt_field_title[i] = strzone("(null)");
382 // TODO: re enable with gametype dependant cvars?
383 if(argc < 3) // no arguments provided
384 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
387 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
391 if(argv(2) == "default" || argv(2) == "expand_default")
393 if(argv(2) == "expand_default")
394 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
395 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
397 else if(argv(2) == "all")
399 string s = "ping pl name |"; // scores without a label
400 FOREACH(Scores, true, {
402 if(it != ps_secondary)
403 if(scores_label(it) != "")
404 s = strcat(s, " ", scores_label(it));
406 if(ps_secondary != ps_primary)
407 s = strcat(s, " ", scores_label(ps_secondary));
408 s = strcat(s, " ", scores_label(ps_primary));
409 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
416 hud_fontsize = HUD_GetFontsize("hud_fontsize");
418 for(i = 1; i < argc - 1; ++i)
421 bool nocomplain = false;
422 if(substring(str, 0, 1) == "?")
425 str = substring(str, 1, strlen(str) - 1);
428 slash = strstrofs(str, "/", 0);
431 pattern = substring(str, 0, slash);
432 str = substring(str, slash + 1, strlen(str) - (slash + 1));
434 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
438 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
439 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
440 str = strtolower(str);
445 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
446 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
447 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
448 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
449 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
450 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
451 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
452 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
453 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
454 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
457 FOREACH(Scores, true, {
458 if (str == strtolower(scores_label(it))) {
460 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
470 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
474 sbt_field[sbt_num_fields] = j;
477 if(j == ps_secondary)
478 have_secondary = true;
483 if(sbt_num_fields >= MAX_SBT_FIELDS)
487 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
489 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
490 have_secondary = true;
491 if(ps_primary == ps_secondary)
492 have_secondary = true;
493 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
495 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
499 strunzone(sbt_field_title[sbt_num_fields]);
500 for(i = sbt_num_fields; i > 0; --i)
502 sbt_field_title[i] = sbt_field_title[i-1];
503 sbt_field_size[i] = sbt_field_size[i-1];
504 sbt_field[i] = sbt_field[i-1];
506 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
507 sbt_field[0] = SP_NAME;
509 LOG_INFO("fixed missing field 'name'");
513 strunzone(sbt_field_title[sbt_num_fields]);
514 for(i = sbt_num_fields; i > 1; --i)
516 sbt_field_title[i] = sbt_field_title[i-1];
517 sbt_field_size[i] = sbt_field_size[i-1];
518 sbt_field[i] = sbt_field[i-1];
520 sbt_field_title[1] = strzone("|");
521 sbt_field[1] = SP_SEPARATOR;
522 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
524 LOG_INFO("fixed missing field '|'");
527 else if(!have_separator)
529 strcpy(sbt_field_title[sbt_num_fields], "|");
530 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
531 sbt_field[sbt_num_fields] = SP_SEPARATOR;
533 LOG_INFO("fixed missing field '|'");
537 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
538 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
539 sbt_field[sbt_num_fields] = ps_secondary;
541 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
545 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
546 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
547 sbt_field[sbt_num_fields] = ps_primary;
549 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
553 sbt_field[sbt_num_fields] = SP_END;
557 vector sbt_field_rgb;
558 string sbt_field_icon0;
559 string sbt_field_icon1;
560 string sbt_field_icon2;
561 vector sbt_field_icon0_rgb;
562 vector sbt_field_icon1_rgb;
563 vector sbt_field_icon2_rgb;
564 string Scoreboard_GetName(entity pl)
566 if(ready_waiting && pl.ready)
568 sbt_field_icon0 = "gfx/scoreboard/player_ready";
572 int f = entcs_GetClientColors(pl.sv_entnum);
574 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
575 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
576 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
577 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
578 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
581 return entcs_GetName(pl.sv_entnum);
584 string Scoreboard_GetField(entity pl, PlayerScoreField field)
586 float tmp, num, denom;
589 sbt_field_rgb = '1 1 1';
590 sbt_field_icon0 = "";
591 sbt_field_icon1 = "";
592 sbt_field_icon2 = "";
593 sbt_field_icon0_rgb = '1 1 1';
594 sbt_field_icon1_rgb = '1 1 1';
595 sbt_field_icon2_rgb = '1 1 1';
600 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
601 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
605 tmp = max(0, min(220, f-80)) / 220;
606 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
612 f = pl.ping_packetloss;
613 tmp = pl.ping_movementloss;
614 if(f == 0 && tmp == 0)
616 str = ftos(ceil(f * 100));
618 str = strcat(str, "~", ftos(ceil(tmp * 100)));
619 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
620 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
624 return Scoreboard_GetName(pl);
627 f = pl.(scores(SP_KILLS));
628 f -= pl.(scores(SP_SUICIDES));
632 num = pl.(scores(SP_KILLS));
633 denom = pl.(scores(SP_DEATHS));
636 sbt_field_rgb = '0 1 0';
637 str = sprintf("%d", num);
638 } else if(num <= 0) {
639 sbt_field_rgb = '1 0 0';
640 str = sprintf("%.1f", num/denom);
642 str = sprintf("%.1f", num/denom);
646 f = pl.(scores(SP_KILLS));
647 f -= pl.(scores(SP_DEATHS));
650 sbt_field_rgb = '0 1 0';
652 sbt_field_rgb = '1 1 1';
654 sbt_field_rgb = '1 0 0';
660 float elo = pl.(scores(SP_ELO));
662 case -1: return "...";
663 case -2: return _("N/A");
664 default: return ftos(elo);
670 float fps = pl.(scores(SP_FPS));
673 sbt_field_rgb = '1 1 1';
674 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
676 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200);
677 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
681 case SP_DMG: case SP_DMGTAKEN:
682 return sprintf("%.1f k", pl.(scores(field)) / 1000);
684 default: case SP_SCORE:
685 tmp = pl.(scores(field));
686 f = scores_flags(field);
687 if(field == ps_primary)
688 sbt_field_rgb = '1 1 0';
689 else if(field == ps_secondary)
690 sbt_field_rgb = '0 1 1';
692 sbt_field_rgb = '1 1 1';
693 return ScoreString(f, tmp);
698 float sbt_fixcolumnwidth_len;
699 float sbt_fixcolumnwidth_iconlen;
700 float sbt_fixcolumnwidth_marginlen;
702 string Scoreboard_FixColumnWidth(int i, string str)
708 sbt_fixcolumnwidth_iconlen = 0;
710 if(sbt_field_icon0 != "")
712 sz = draw_getimagesize(sbt_field_icon0);
714 if(sbt_fixcolumnwidth_iconlen < f)
715 sbt_fixcolumnwidth_iconlen = f;
718 if(sbt_field_icon1 != "")
720 sz = draw_getimagesize(sbt_field_icon1);
722 if(sbt_fixcolumnwidth_iconlen < f)
723 sbt_fixcolumnwidth_iconlen = f;
726 if(sbt_field_icon2 != "")
728 sz = draw_getimagesize(sbt_field_icon2);
730 if(sbt_fixcolumnwidth_iconlen < f)
731 sbt_fixcolumnwidth_iconlen = f;
734 if(sbt_fixcolumnwidth_iconlen != 0)
736 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
737 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
740 sbt_fixcolumnwidth_marginlen = 0;
742 if(sbt_field[i] == SP_NAME) // name gets all remaining space
745 float remaining_space = 0;
746 for(j = 0; j < sbt_num_fields; ++j)
748 if (sbt_field[i] != SP_SEPARATOR)
749 remaining_space += sbt_field_size[j] + hud_fontsize.x;
750 sbt_field_size[i] = panel_size.x - remaining_space;
752 if (sbt_fixcolumnwidth_iconlen != 0)
753 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
754 float namesize = panel_size.x - remaining_space;
755 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
756 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
758 max_namesize = vid_conwidth - remaining_space;
761 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
763 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
764 if(sbt_field_size[i] < f)
765 sbt_field_size[i] = f;
770 void Scoreboard_initFieldSizes()
772 for(int i = 0; i < sbt_num_fields; ++i)
774 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
775 Scoreboard_FixColumnWidth(i, "");
779 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
782 vector column_dim = eY * panel_size.y;
784 column_dim.y -= 1.25 * hud_fontsize.y;
785 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
786 pos.x += hud_fontsize.x * 0.5;
787 for(i = 0; i < sbt_num_fields; ++i)
789 if(sbt_field[i] == SP_SEPARATOR)
791 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
794 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
795 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
796 pos.x += column_dim.x;
798 if(sbt_field[i] == SP_SEPARATOR)
800 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
801 for(i = sbt_num_fields - 1; i > 0; --i)
803 if(sbt_field[i] == SP_SEPARATOR)
806 pos.x -= sbt_field_size[i];
811 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
812 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
815 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
816 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
817 pos.x -= hud_fontsize.x;
822 pos.y += 1.25 * hud_fontsize.y;
826 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
828 TC(bool, is_self); TC(int, pl_number);
830 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
832 vector h_pos = item_pos;
833 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
834 // alternated rows highlighting
836 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
837 else if((sbt_highlight) && (!(pl_number % 2)))
838 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
840 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
842 vector pos = item_pos;
843 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
845 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);
847 pos.x += hud_fontsize.x * 0.5;
848 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
849 vector tmp = '0 0 0';
851 PlayerScoreField field;
852 for(i = 0; i < sbt_num_fields; ++i)
854 field = sbt_field[i];
855 if(field == SP_SEPARATOR)
858 if(is_spec && field != SP_NAME && field != SP_PING) {
859 pos.x += sbt_field_size[i] + hud_fontsize.x;
862 str = Scoreboard_GetField(pl, field);
863 str = Scoreboard_FixColumnWidth(i, str);
865 pos.x += sbt_field_size[i] + hud_fontsize.x;
867 if(field == SP_NAME) {
868 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
869 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
871 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
872 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
875 tmp.x = sbt_field_size[i] + hud_fontsize.x;
876 if(sbt_field_icon0 != "")
877 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
878 if(sbt_field_icon1 != "")
879 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
880 if(sbt_field_icon2 != "")
881 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
884 if(sbt_field[i] == SP_SEPARATOR)
886 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
887 for(i = sbt_num_fields-1; i > 0; --i)
889 field = sbt_field[i];
890 if(field == SP_SEPARATOR)
893 if(is_spec && field != SP_NAME && field != SP_PING) {
894 pos.x -= sbt_field_size[i] + hud_fontsize.x;
898 str = Scoreboard_GetField(pl, field);
899 str = Scoreboard_FixColumnWidth(i, str);
901 if(field == SP_NAME) {
902 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
903 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
905 tmp.x = sbt_fixcolumnwidth_len;
906 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
909 tmp.x = sbt_field_size[i];
910 if(sbt_field_icon0 != "")
911 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
912 if(sbt_field_icon1 != "")
913 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
914 if(sbt_field_icon2 != "")
915 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
916 pos.x -= sbt_field_size[i] + hud_fontsize.x;
921 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
924 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
927 vector h_pos = item_pos;
928 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
930 bool complete = (this_team == NUM_SPECTATOR);
933 if((sbt_highlight) && (!(pl_number % 2)))
934 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
936 vector pos = item_pos;
937 pos.x += hud_fontsize.x * 0.5;
938 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
940 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
942 width_limit -= stringwidth("...", false, hud_fontsize);
943 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
944 static float max_name_width = 0;
947 float min_fieldsize = 0;
948 float fieldpadding = hud_fontsize.x * 0.25;
949 if(this_team == NUM_SPECTATOR)
951 if(autocvar_hud_panel_scoreboard_spectators_showping)
952 min_fieldsize = stringwidth("999", false, hud_fontsize);
954 else if(autocvar_hud_panel_scoreboard_others_showscore)
955 min_fieldsize = stringwidth("99", false, hud_fontsize);
956 for(i = 0; pl; pl = pl.sort_next)
958 if(pl.team != this_team)
964 if(this_team == NUM_SPECTATOR)
966 if(autocvar_hud_panel_scoreboard_spectators_showping)
967 field = Scoreboard_GetField(pl, SP_PING);
969 else if(autocvar_hud_panel_scoreboard_others_showscore)
970 field = Scoreboard_GetField(pl, SP_SCORE);
972 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
973 float column_width = stringwidth(str, true, hud_fontsize);
974 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
976 if(column_width > max_name_width)
977 max_name_width = column_width;
978 column_width = max_name_width;
982 fieldsize = stringwidth(field, false, hud_fontsize);
983 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
986 if(pos.x + column_width > width_limit)
991 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
996 pos.x = item_pos.x + hud_fontsize.x * 0.5;
997 pos.y += hud_fontsize.y * 1.25;
1001 vector name_pos = pos;
1002 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1003 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1004 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1007 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1008 h_size.y = hud_fontsize.y;
1009 vector field_pos = pos;
1010 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1011 field_pos.x += column_width - h_size.x;
1013 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1014 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1015 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1019 h_size.x = column_width + hud_fontsize.x * 0.25;
1020 h_size.y = hud_fontsize.y;
1021 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
1023 pos.x += column_width;
1024 pos.x += hud_fontsize.x;
1026 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1029 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1031 int max_players = 999;
1032 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1034 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1037 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1038 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1039 height /= team_count;
1042 height -= panel_bg_padding * 2; // - padding
1043 max_players = floor(height / (hud_fontsize.y * 1.25));
1044 if(max_players <= 1)
1046 if(max_players == tm.team_size)
1051 entity me = playerslots[current_player];
1053 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1054 panel_size.y += panel_bg_padding * 2;
1057 vector end_pos = panel_pos + eY * (panel_size.y + 0.5* hud_fontsize.y);
1058 if(panel.current_panel_bg != "0")
1059 end_pos.y += panel_bg_border * 2;
1061 if(panel_bg_padding)
1063 panel_pos += '1 1 0' * panel_bg_padding;
1064 panel_size -= '2 2 0' * panel_bg_padding;
1068 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1072 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1074 pos.y += 1.25 * hud_fontsize.y;
1077 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1079 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1082 // print header row and highlight columns
1083 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1085 // fill the table and draw the rows
1086 bool is_self = false;
1087 bool self_shown = false;
1089 for(pl = players.sort_next; pl; pl = pl.sort_next)
1091 if(pl.team != tm.team)
1093 if(i == max_players - 2 && pl != me)
1095 if(!self_shown && me.team == tm.team)
1097 Scoreboard_DrawItem(pos, rgb, me, true, i);
1099 pos.y += 1.25 * hud_fontsize.y;
1103 if(i >= max_players - 1)
1105 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1108 is_self = (pl.sv_entnum == current_player);
1109 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1112 pos.y += 1.25 * hud_fontsize.y;
1116 panel_size.x += panel_bg_padding * 2; // restore initial width
1120 bool Scoreboard_WouldDraw()
1122 if (MUTATOR_CALLHOOK(DrawScoreboard))
1124 else if (QuickMenu_IsOpened())
1126 else if (HUD_Radar_Clickable())
1128 else if (scoreboard_showscores)
1130 else if (intermission == 1)
1132 else if (intermission == 2)
1134 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !ISGAMETYPE(CTS)
1135 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1139 else if (scoreboard_showscores_force)
1144 float average_accuracy;
1145 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1149 if (scoreboard_fade_alpha == 1)
1150 scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
1152 scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
1154 vector initial_pos = pos;
1156 WepSet weapons_stat = WepSet_GetFromStat();
1157 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1158 int disownedcnt = 0;
1160 FOREACH(Weapons, it != WEP_Null, {
1161 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1163 WepSet set = it.m_wepset;
1164 if(it.spawnflags & WEP_TYPE_OTHER)
1169 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1171 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1178 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1179 if (weapon_cnt <= 0) return pos;
1182 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1184 int columnns = ceil(weapon_cnt / rows);
1186 float weapon_height = 29;
1187 float height = hud_fontsize.y + weapon_height;
1189 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);
1190 pos.y += 1.25 * hud_fontsize.y;
1191 if(panel.current_panel_bg != "0")
1192 pos.y += panel_bg_border;
1195 panel_size.y = height * rows;
1196 panel_size.y += panel_bg_padding * 2;
1198 float panel_bg_alpha_save = panel_bg_alpha;
1199 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1201 panel_bg_alpha = panel_bg_alpha_save;
1203 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1204 if(panel.current_panel_bg != "0")
1205 end_pos.y += panel_bg_border * 2;
1207 if(panel_bg_padding)
1209 panel_pos += '1 1 0' * panel_bg_padding;
1210 panel_size -= '2 2 0' * panel_bg_padding;
1214 vector tmp = panel_size;
1216 float weapon_width = tmp.x / columnns / rows;
1219 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1223 // column highlighting
1224 for (int i = 0; i < columnns; ++i)
1226 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);
1229 for (int i = 0; i < rows; ++i)
1230 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1233 average_accuracy = 0;
1234 int weapons_with_stats = 0;
1236 pos.x += weapon_width / 2;
1238 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1241 Accuracy_LoadColors();
1243 float oldposx = pos.x;
1247 FOREACH(Weapons, it != WEP_Null, {
1248 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1250 WepSet set = it.m_wepset;
1251 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1253 if (it.spawnflags & WEP_TYPE_OTHER)
1257 if (weapon_stats >= 0)
1258 weapon_alpha = sbt_fg_alpha;
1260 weapon_alpha = 0.2 * sbt_fg_alpha;
1263 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1265 if (weapon_stats >= 0) {
1266 weapons_with_stats += 1;
1267 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1270 s = sprintf("%d%%", weapon_stats * 100);
1273 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1275 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1276 rgb = Accuracy_GetColor(weapon_stats);
1278 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1280 tmpos.x += weapon_width * rows;
1281 pos.x += weapon_width * rows;
1282 if (rows == 2 && column == columnns - 1) {
1290 if (weapons_with_stats)
1291 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1293 panel_size.x += panel_bg_padding * 2; // restore initial width
1295 if (scoreboard_acc_fade_alpha == 1)
1297 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1300 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1302 float scoreboard_acc_fade_alpha_save = scoreboard_acc_fade_alpha; // debug
1303 scoreboard_acc_fade_alpha = 1; // debug: make Item Stats always visible
1305 float initial_posx = pos.x;
1306 int disownedcnt = 0;
1307 FOREACH(Items, true, {
1308 int q = g_inventory.inv_items[it.m_id];
1309 //q = 1; // debug: display all items
1310 if (!q) ++disownedcnt;
1313 int n = Items_COUNT - disownedcnt;
1314 if (n <= 0) return pos;
1316 int rows = (autocvar_hud_panel_scoreboard_accuracy_doublerows && n >= floor(Items_COUNT / 2)) ? 2 : 1;
1317 int columnns = ceil(n / rows);
1320 float fontsize = height * 1/3;
1321 float item_height = height * 2/3;
1323 drawstring(pos, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1324 pos.y += 1.25 * hud_fontsize.y;
1325 if(panel.current_panel_bg != "0")
1326 pos.y += panel_bg_border;
1329 panel_size.y = height * rows;
1330 panel_size.y += panel_bg_padding * 2;
1332 float panel_bg_alpha_save = panel_bg_alpha;
1333 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1335 panel_bg_alpha = panel_bg_alpha_save;
1337 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1338 if(panel.current_panel_bg != "0")
1339 end_pos.y += panel_bg_border * 2;
1341 if(panel_bg_padding)
1343 panel_pos += '1 1 0' * panel_bg_padding;
1344 panel_size -= '2 2 0' * panel_bg_padding;
1348 vector tmp = panel_size;
1350 float item_width = tmp.x / columnns / rows;
1353 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1357 // column highlighting
1358 for (int i = 0; i < columnns; ++i)
1360 drawfill(pos + '1 0 0' * item_width * rows * i, '0 1 0' * height * rows + '1 0 0' * item_width * rows, '0 0 0', panel_bg_alpha * 0.2, DRAWFLAG_NORMAL);
1363 for (int i = 0; i < rows; ++i)
1364 drawfill(pos + '0 1 0' * item_height + '0 1 0' * height * i, '1 0 0' * panel_size.x + '0 1 0' * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1368 pos.x += item_width / 2;
1370 float oldposx = pos.x;
1374 FOREACH(Items, true, {
1375 int n = g_inventory.inv_items[it.m_id];
1376 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1377 if (n <= 0) continue;
1378 drawpic_aspect_skin(tmpos, it.m_icon, '1 0 0' * item_width + '0 1 0' * item_height, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1380 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1381 drawstring(tmpos + '1 0 0' * padding + '0 1 0' * item_height, s, '1 1 0' * fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1382 tmpos.x += item_width * rows;
1383 pos.x += item_width * rows;
1384 if (rows == 2 && column == columnns - 1) {
1392 pos.y += 1.25 * hud_fontsize.y;
1393 pos.x = initial_posx;
1395 panel_size.x += panel_bg_padding * 2; // restore initial width
1397 scoreboard_acc_fade_alpha = scoreboard_acc_fade_alpha_save; // debug
1401 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1403 pos.x += hud_fontsize.x * 0.25;
1404 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1405 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1406 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1408 pos.y += hud_fontsize.y;
1413 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1414 float stat_secrets_found, stat_secrets_total;
1415 float stat_monsters_killed, stat_monsters_total;
1419 // get monster stats
1420 stat_monsters_killed = STAT(MONSTERS_KILLED);
1421 stat_monsters_total = STAT(MONSTERS_TOTAL);
1423 // get secrets stats
1424 stat_secrets_found = STAT(SECRETS_FOUND);
1425 stat_secrets_total = STAT(SECRETS_TOTAL);
1427 // get number of rows
1428 if(stat_secrets_total)
1430 if(stat_monsters_total)
1433 // if no rows, return
1437 // draw table header
1438 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1439 pos.y += 1.25 * hud_fontsize.y;
1440 if(panel.current_panel_bg != "0")
1441 pos.y += panel_bg_border;
1444 panel_size.y = hud_fontsize.y * rows;
1445 panel_size.y += panel_bg_padding * 2;
1448 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1449 if(panel.current_panel_bg != "0")
1450 end_pos.y += panel_bg_border * 2;
1452 if(panel_bg_padding)
1454 panel_pos += '1 1 0' * panel_bg_padding;
1455 panel_size -= '2 2 0' * panel_bg_padding;
1459 vector tmp = panel_size;
1462 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1465 if(stat_monsters_total)
1467 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1468 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1472 if(stat_secrets_total)
1474 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1475 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1478 panel_size.x += panel_bg_padding * 2; // restore initial width
1483 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1486 RANKINGS_RECEIVED_CNT = 0;
1487 for (i=RANKINGS_CNT-1; i>=0; --i)
1489 ++RANKINGS_RECEIVED_CNT;
1491 if (RANKINGS_RECEIVED_CNT == 0)
1494 vector hl_rgb = rgb + '0.5 0.5 0.5';
1496 pos.y += hud_fontsize.y;
1497 drawstring(pos + eX * panel_bg_padding, ((ISGAMETYPE(CTF)) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1498 pos.y += 1.25 * hud_fontsize.y;
1499 if(panel.current_panel_bg != "0")
1500 pos.y += panel_bg_border;
1505 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1507 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1512 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1514 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1518 float ranksize = 3 * hud_fontsize.x;
1519 float timesize = 5 * hud_fontsize.x;
1520 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1521 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1522 columns = min(columns, RANKINGS_RECEIVED_CNT);
1524 // expand name column to fill the entire row
1525 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1526 namesize += available_space;
1527 columnsize.x += available_space;
1529 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1530 panel_size.y += panel_bg_padding * 2;
1534 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1535 if(panel.current_panel_bg != "0")
1536 end_pos.y += panel_bg_border * 2;
1538 if(panel_bg_padding)
1540 panel_pos += '1 1 0' * panel_bg_padding;
1541 panel_size -= '2 2 0' * panel_bg_padding;
1547 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1549 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1551 int column = 0, j = 0;
1552 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1553 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1560 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1561 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1562 else if(!((j + column) & 1) && sbt_highlight)
1563 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1565 str = count_ordinal(i+1);
1566 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1567 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1568 str = ColorTranslateRGB(grecordholder[i]);
1570 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1571 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1573 pos.y += 1.25 * hud_fontsize.y;
1575 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1579 pos.x += panel_size.x / columns;
1580 pos.y = panel_pos.y;
1583 strfree(zoned_name_self);
1585 panel_size.x += panel_bg_padding * 2; // restore initial width
1589 float scoreboard_time;
1590 bool have_weapon_stats;
1591 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1593 if (ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || ISGAMETYPE(NEXBALL))
1595 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1598 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1599 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1605 if (!have_weapon_stats)
1607 FOREACH(Weapons, it != WEP_Null, {
1608 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1609 if (weapon_stats >= 0)
1611 have_weapon_stats = true;
1615 if (!have_weapon_stats)
1622 void Scoreboard_Draw()
1624 if(!autocvar__hud_configure)
1626 if(!hud_draw_maximized) return;
1628 // frametime checks allow to toggle the scoreboard even when the game is paused
1629 if(scoreboard_active) {
1630 if (scoreboard_fade_alpha < 1)
1631 scoreboard_time = time;
1632 if(hud_configure_menu_open == 1)
1633 scoreboard_fade_alpha = 1;
1634 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1635 if (scoreboard_fadeinspeed && frametime)
1636 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1638 scoreboard_fade_alpha = 1;
1639 if(hud_fontsize_str != autocvar_hud_fontsize)
1641 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1642 Scoreboard_initFieldSizes();
1643 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1647 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1648 if (scoreboard_fadeoutspeed && frametime)
1649 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1651 scoreboard_fade_alpha = 0;
1654 if (!scoreboard_fade_alpha)
1656 scoreboard_acc_fade_alpha = 0;
1661 scoreboard_fade_alpha = 0;
1663 if (autocvar_hud_panel_scoreboard_dynamichud)
1666 HUD_Scale_Disable();
1668 if(scoreboard_fade_alpha <= 0)
1670 panel_fade_alpha *= scoreboard_fade_alpha;
1671 HUD_Panel_LoadCvars();
1673 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1674 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1675 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1676 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1677 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1678 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1680 // don't overlap with con_notify
1681 if(!autocvar__hud_configure)
1682 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1684 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1685 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1686 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1687 panel_size.x = fixed_scoreboard_width;
1689 Scoreboard_UpdatePlayerTeams();
1691 vector pos = panel_pos;
1696 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1698 // Begin of Game Info Section
1699 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1700 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1702 // Game Info: Game Type
1703 str = MapInfo_Type_ToText(gametype);
1704 draw_beginBoldFont();
1705 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);
1708 // Game Info: Game Detail
1710 str = ""; // optionally "^7Limits: "
1711 tl = STAT(TIMELIMIT);
1712 fl = STAT(FRAGLIMIT);
1713 ll = STAT(LEADLIMIT);
1715 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1716 if(!ISGAMETYPE(LMS))
1721 str = strcat(str, "^7 / "); // delimiter
1724 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1725 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1726 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1727 TranslateScoresLabel(teamscores_label(ts_primary))));
1731 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1732 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1733 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1734 TranslateScoresLabel(scores_label(ps_primary))));
1739 if(tl > 0 || fl > 0)
1740 str = strcat(str, "^7 / "); // delimiter
1743 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1744 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1745 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1746 TranslateScoresLabel(teamscores_label(ts_primary))));
1750 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1751 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1752 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1753 TranslateScoresLabel(scores_label(ps_primary))));
1758 pos.y += sb_gameinfo_type_fontsize.y;
1759 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
1761 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1762 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1763 // End of Game Info Section
1765 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1766 if(panel.current_panel_bg != "0")
1767 pos.y += panel_bg_border;
1769 // Draw the scoreboard
1770 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1773 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1777 vector panel_bg_color_save = panel_bg_color;
1778 vector team_score_baseoffset;
1779 vector team_size_baseoffset;
1780 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1782 // put team score to the left of scoreboard (and team size to the right)
1783 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1784 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1785 if(panel.current_panel_bg != "0")
1787 team_score_baseoffset.x -= panel_bg_border;
1788 team_size_baseoffset.x += panel_bg_border;
1793 // put team score to the right of scoreboard (and team size to the left)
1794 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1795 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1796 if(panel.current_panel_bg != "0")
1798 team_score_baseoffset.x += panel_bg_border;
1799 team_size_baseoffset.x -= panel_bg_border;
1803 int team_size_total = 0;
1804 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1806 // calculate team size total (sum of all team sizes)
1807 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1808 if(tm.team != NUM_SPECTATOR)
1809 team_size_total += tm.team_size;
1812 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1814 if(tm.team == NUM_SPECTATOR)
1819 draw_beginBoldFont();
1820 vector rgb = Team_ColorRGB(tm.team);
1821 str = ftos(tm.(teamscores(ts_primary)));
1822 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1824 // team score on the left (default)
1825 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1829 // team score on the right
1830 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1832 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1834 // team size (if set to show on the side)
1835 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1837 // calculate the starting position for the whole team size info string
1838 str = sprintf("%d/%d", tm.team_size, team_size_total);
1839 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1841 // team size on the left
1842 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1846 // team size on the right
1847 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1849 str = sprintf("%d", tm.team_size);
1850 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1851 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1852 str = sprintf("/%d", team_size_total);
1853 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1857 // secondary score, e.g. keyhunt
1858 if(ts_primary != ts_secondary)
1860 str = ftos(tm.(teamscores(ts_secondary)));
1861 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1864 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1869 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1872 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1875 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1876 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1877 else if(panel_bg_color_team > 0)
1878 panel_bg_color = rgb * panel_bg_color_team;
1880 panel_bg_color = rgb;
1881 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1883 panel_bg_color = panel_bg_color_save;
1887 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1888 if(tm.team != NUM_SPECTATOR)
1891 // display it anyway
1892 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1895 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1896 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1897 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
1899 if(ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || (autocvar_hud_panel_scoreboard_ctf_leaderboard && ISGAMETYPE(CTF) && STAT(CTF_SHOWLEADERBOARD))) {
1900 if(race_speedaward) {
1901 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);
1902 pos.y += 1.25 * hud_fontsize.y;
1904 if(race_speedaward_alltimebest) {
1905 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);
1906 pos.y += 1.25 * hud_fontsize.y;
1908 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1911 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1914 for(pl = players.sort_next; pl; pl = pl.sort_next)
1916 if(pl.team == NUM_SPECTATOR)
1918 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1919 if(tm.team == NUM_SPECTATOR)
1921 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1922 draw_beginBoldFont();
1923 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1925 pos.y += 1.25 * hud_fontsize.y;
1927 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1928 pos.y += 1.25 * hud_fontsize.y;
1935 // print information about respawn status
1936 float respawn_time = STAT(RESPAWN_TIME);
1940 if(respawn_time < 0)
1942 // a negative number means we are awaiting respawn, time value is still the same
1943 respawn_time *= -1; // remove mark now that we checked it
1945 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1946 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1948 str = sprintf(_("^1Respawning in ^3%s^1..."),
1949 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1950 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1952 count_seconds(ceil(respawn_time - time))
1956 else if(time < respawn_time)
1958 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1959 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1960 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1962 count_seconds(ceil(respawn_time - time))
1966 else if(time >= respawn_time)
1967 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1969 pos.y += 1.2 * hud_fontsize.y;
1970 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1973 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;