Add fixed core HUD and shared service window for T5 and T6
This commit is contained in:
+112
-171
@@ -1,4 +1,4 @@
|
||||
/* tws_t5_hud.gsc — top-left Discord URL, timers, zombie count and health bar. */
|
||||
/* tws_t5_hud.gsc - fixed upper-right core HUD, visible from connection. */
|
||||
|
||||
#include maps\_utility;
|
||||
#include common_scripts\utility;
|
||||
@@ -13,34 +13,90 @@ init()
|
||||
level thread round_watcher();
|
||||
level thread game_over_watcher();
|
||||
|
||||
scripts\sp\tws_t5_common::register_connect_handler(::hud_connect);
|
||||
scripts\sp\tws_t5_common::register_spawn_handler(::hud_start);
|
||||
scripts\sp\tws_t5_common::register_connect_handler(::hud_discord);
|
||||
}
|
||||
|
||||
// One static label per connection, including newcomers waiting to spawn.
|
||||
hud_discord()
|
||||
// Connection construction never seeds clocks or reads player gameplay state.
|
||||
// The first-spawn fallback also handles a spawn before the connect callback.
|
||||
hud_connect()
|
||||
{
|
||||
self endon("disconnect");
|
||||
|
||||
label = scripts\sp\tws_t5_common::make_text_elem(
|
||||
self, 10, 10, "left", 1.2, (0.65, 0.65, 0.65));
|
||||
label SetText("https://discord.tws.lol");
|
||||
if (isDefined(self.tws_hud_elems))
|
||||
return;
|
||||
|
||||
elems = [];
|
||||
elems[elems.size] = label;
|
||||
self thread scripts\sp\tws_t5_common::destroy_on_disconnect(elems);
|
||||
self.tws_hud_elems = [];
|
||||
background = scripts\sp\tws_t5_common::make_panel_shader(
|
||||
self, 0, 0, level.hud_panel_width, level.hud_core_height,
|
||||
level.hud_c_panel, level.hud_panel_alpha, 0);
|
||||
self.tws_hud_elems[self.tws_hud_elems.size] = background;
|
||||
|
||||
self hud_text(level.hud_pad, level.hud_row_header, "left",
|
||||
level.hud_font_small, level.hud_c_name, "TWS ZOMBIES");
|
||||
self hud_text(level.hud_panel_width - level.hud_pad, level.hud_row_header,
|
||||
"right", level.hud_font_small, level.hud_c_dim, "discord.tws.lol");
|
||||
|
||||
self hud_text(level.hud_pad, level.hud_row_timers, "left",
|
||||
level.hud_font_small, level.hud_c_label, "MATCH");
|
||||
self.tws_hud_match = self hud_text(104, level.hud_row_timers - 2, "right",
|
||||
level.hud_font_mid, level.hud_c_count, "--:--");
|
||||
self hud_text(114, level.hud_row_timers, "left",
|
||||
level.hud_font_small, level.hud_c_label, "ROUND");
|
||||
self.tws_hud_round = self hud_text(level.hud_panel_width - level.hud_pad,
|
||||
level.hud_row_timers - 2, "right",
|
||||
level.hud_font_mid, level.hud_c_count, "--:--");
|
||||
|
||||
self hud_text(level.hud_pad, level.hud_row_stats, "left",
|
||||
level.hud_font_small, level.hud_c_label, "ZOMBIES");
|
||||
self.tws_hud_count = self hud_text(67, level.hud_row_stats, "left",
|
||||
level.hud_font_mid, level.hud_c_count, "--");
|
||||
self hud_text(132, level.hud_row_stats, "left",
|
||||
level.hud_font_small, level.hud_c_label, "HP");
|
||||
|
||||
// Independent numeric elements avoid a new config string on every tick.
|
||||
self.tws_hud_health = self hud_text(174, level.hud_row_stats, "right",
|
||||
level.hud_font_mid, level.hud_c_count, "--");
|
||||
self.tws_hud_slash = self hud_text(181, level.hud_row_stats, "center",
|
||||
level.hud_font_mid, level.hud_c_count, "/");
|
||||
self.tws_hud_maxhealth = self hud_text(level.hud_panel_width - level.hud_pad,
|
||||
level.hud_row_stats, "right", level.hud_font_mid, level.hud_c_count, "--");
|
||||
|
||||
track = scripts\sp\tws_t5_common::make_panel_shader(
|
||||
self, level.hud_pad, level.hud_row_bar,
|
||||
level.hud_bar_width, level.hud_bar_height, (0.18, 0.22, 0.19), 0.8, 10);
|
||||
self.tws_hud_elems[self.tws_hud_elems.size] = track;
|
||||
self.tws_hud_bar = scripts\sp\tws_t5_common::make_panel_shader(
|
||||
self, level.hud_pad, level.hud_row_bar,
|
||||
level.hud_bar_width, level.hud_bar_height, level.hud_c_high, 0, 11);
|
||||
self.tws_hud_elems[self.tws_hud_elems.size] = self.tws_hud_bar;
|
||||
|
||||
self thread scripts\sp\tws_t5_common::destroy_on_disconnect(self.tws_hud_elems);
|
||||
}
|
||||
|
||||
hud_text(x, y, align, scale, colour, text)
|
||||
{
|
||||
elem = scripts\sp\tws_t5_common::make_panel_text(self, x, y, align, scale, colour);
|
||||
elem SetText(text);
|
||||
self.tws_hud_elems[self.tws_hud_elems.size] = elem;
|
||||
return elem;
|
||||
}
|
||||
|
||||
hud_start()
|
||||
{
|
||||
self endon("disconnect");
|
||||
|
||||
self hud_connect();
|
||||
if (isDefined(self.tws_hud_bound) || is_true(level.tws_ended))
|
||||
return;
|
||||
|
||||
self.tws_hud_bound = true;
|
||||
self thread hud_timer_row();
|
||||
self thread hud_zombie_counter();
|
||||
self thread hud_health_display();
|
||||
}
|
||||
|
||||
// BO1 maps use different end-of-match notify names; race all three
|
||||
// Race the map-dependent game-over notifications once at level scope.
|
||||
game_over_watcher()
|
||||
{
|
||||
level thread game_over_via("end_game");
|
||||
@@ -52,17 +108,14 @@ game_over_via(notify_name)
|
||||
{
|
||||
level endon("hud_game_over");
|
||||
level waittill(notify_name);
|
||||
level.tws_ended = true;
|
||||
level notify("hud_game_over");
|
||||
}
|
||||
|
||||
/*
|
||||
Watch level.round_number rather than a notify — the notify names vary
|
||||
between BO1 map scripts, the variable doesn't. Seed on OBSERVED
|
||||
transitions only: many maps pre-set round_number = 1 at level init
|
||||
and we don't want getTime() captured there (see NOTES.md).
|
||||
*/
|
||||
// Pre-set round 1 is seeded on first spawn; only observed transitions seed here.
|
||||
round_watcher()
|
||||
{
|
||||
level endon("hud_game_over");
|
||||
last = 0;
|
||||
|
||||
if (isDefined(level.round_number))
|
||||
@@ -92,7 +145,7 @@ hud_timer_row()
|
||||
{
|
||||
self endon("disconnect");
|
||||
|
||||
// fallback seed for the round-1-already-active case (round_watcher misses it)
|
||||
// A visible panel for a waiting newcomer must not start a pre-set round 1.
|
||||
if (!isDefined(level.match_start_time) &&
|
||||
isDefined(level.round_number) &&
|
||||
level.round_number >= 1)
|
||||
@@ -102,59 +155,17 @@ hud_timer_row()
|
||||
level notify("hud_match_started");
|
||||
}
|
||||
|
||||
match = scripts\sp\tws_t5_common::make_text_elem(
|
||||
self, level.hud_x, level.hud_row_match,
|
||||
"left", level.hud_font_small, level.hud_c_dim);
|
||||
match.alpha = 0; // revealed by match_timer_bind when round 1 opens
|
||||
|
||||
round = scripts\sp\tws_t5_common::make_text_elem(
|
||||
self, level.hud_x, level.hud_row_round,
|
||||
"left", level.hud_font_small, level.hud_c_dim);
|
||||
round = self.tws_hud_round;
|
||||
round SetTimerUp(0);
|
||||
|
||||
// name only shows in co-op — your own name is noise in solo
|
||||
name = scripts\sp\tws_t5_common::make_text_elem(
|
||||
self, level.hud_x, level.hud_row_name,
|
||||
"left", level.hud_font_small, level.hud_c_name);
|
||||
|
||||
if (isDefined(self.name))
|
||||
name SetText(self.name);
|
||||
else
|
||||
name SetText(self.playername);
|
||||
|
||||
name.alpha = 0;
|
||||
|
||||
elems = [];
|
||||
elems[elems.size] = match;
|
||||
elems[elems.size] = round;
|
||||
elems[elems.size] = name;
|
||||
self thread scripts\sp\tws_t5_common::destroy_on_disconnect(elems);
|
||||
|
||||
self thread reset_timer_each_round(round);
|
||||
self thread match_timer_bind(match);
|
||||
self thread freeze_timers_on_game_over(match, round);
|
||||
|
||||
coop = -1;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
is_coop = 0;
|
||||
|
||||
if (isDefined(level.players) && level.players.size > 1)
|
||||
is_coop = 1;
|
||||
|
||||
if (is_coop != coop)
|
||||
{
|
||||
name.alpha = is_coop;
|
||||
coop = is_coop;
|
||||
}
|
||||
|
||||
wait (1);
|
||||
}
|
||||
self thread match_timer_bind(self.tws_hud_match);
|
||||
self thread freeze_timers_on_game_over(self.tws_hud_match, round);
|
||||
}
|
||||
|
||||
reset_timer_each_round(timer)
|
||||
{
|
||||
level endon("hud_game_over");
|
||||
self endon("disconnect");
|
||||
|
||||
for (;;)
|
||||
@@ -166,26 +177,20 @@ reset_timer_each_round(timer)
|
||||
}
|
||||
}
|
||||
|
||||
// SetTimerUp only ticks with arg 0 on T5 SP; late joiners start at 0
|
||||
// T5 SP only ticks with a zero seed; late joiners retain that engine limitation.
|
||||
match_timer_bind(elem)
|
||||
{
|
||||
self endon("disconnect");
|
||||
level endon("hud_game_over");
|
||||
|
||||
if (!isDefined(level.match_start_time))
|
||||
level waittill("hud_match_started");
|
||||
|
||||
if (isDefined(elem))
|
||||
{
|
||||
elem SetTimerUp(0);
|
||||
elem.alpha = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Level-scope trigger only — per-player death/health polling is intercepted
|
||||
by the zombiemode scripts. Freeze value math relies on the timers being
|
||||
anchored at first spawn / first round transition, not level init.
|
||||
*/
|
||||
// The panel remains visible at game over, with authoritative frozen clocks.
|
||||
freeze_timers_on_game_over(match_elem, round_elem)
|
||||
{
|
||||
self endon("disconnect");
|
||||
@@ -201,27 +206,16 @@ freeze_timers_on_game_over(match_elem, round_elem)
|
||||
hud_zombie_counter()
|
||||
{
|
||||
self endon("disconnect");
|
||||
level endon("hud_game_over");
|
||||
|
||||
label = scripts\sp\tws_t5_common::make_text_elem(
|
||||
self, level.hud_x, level.hud_row_count,
|
||||
"left", level.hud_font_large, level.hud_c_label);
|
||||
label SetText("Zombies");
|
||||
|
||||
count = scripts\sp\tws_t5_common::make_text_elem(
|
||||
self, level.hud_col_count, level.hud_row_count,
|
||||
"left", level.hud_font_large, level.hud_c_count);
|
||||
count SetValue(0);
|
||||
|
||||
elems = [];
|
||||
elems[elems.size] = label;
|
||||
elems[elems.size] = count;
|
||||
self thread scripts\sp\tws_t5_common::destroy_on_disconnect(elems);
|
||||
|
||||
count = self.tws_hud_count;
|
||||
last = -1;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
remaining = get_enemy_count() + level.zombie_total;
|
||||
remaining = get_enemy_count();
|
||||
if (isDefined(level.zombie_total))
|
||||
remaining += max(0, level.zombie_total);
|
||||
|
||||
if (remaining != last)
|
||||
{
|
||||
@@ -253,117 +247,64 @@ get_enemy_count()
|
||||
hud_health_display()
|
||||
{
|
||||
self endon("disconnect");
|
||||
|
||||
if (!isDefined(self.maxhealth) || self.maxhealth <= 0)
|
||||
self.maxhealth = 100;
|
||||
level endon("hud_game_over");
|
||||
|
||||
bar_width = level.hud_bar_width;
|
||||
|
||||
// -2 on both axes so the back frames the bar with 2px padding
|
||||
back = scripts\sp\tws_t5_common::make_shader_elem(self, level.hud_x - 2, level.hud_row_bar - 2);
|
||||
back.alpha = 0.5;
|
||||
back.color = (0, 0, 0);
|
||||
back SetShader("black", bar_width + 4, level.hud_bar_height + 4);
|
||||
|
||||
bar = scripts\sp\tws_t5_common::make_shader_elem(self, level.hud_x, level.hud_row_bar);
|
||||
bar.color = level.hud_c_high;
|
||||
bar SetShader("white", bar_width, level.hud_bar_height);
|
||||
|
||||
/*
|
||||
"87/160" is three elements, not a rebuilt string. SetText allocates
|
||||
a config string per call; per-tick rebuilds crash the server.
|
||||
cur is right-aligned so digits grow leftward and the slash + max
|
||||
never shift.
|
||||
*/
|
||||
cur = scripts\sp\tws_t5_common::make_text_elem(
|
||||
self, level.hud_col_health + 26, level.hud_row_bar + 3,
|
||||
"right", level.hud_font_mid, level.hud_c_high);
|
||||
cur SetValue(self.health);
|
||||
|
||||
slash = scripts\sp\tws_t5_common::make_text_elem(
|
||||
self, level.hud_col_health + 30, level.hud_row_bar + 3,
|
||||
"center", level.hud_font_mid, level.hud_c_high);
|
||||
slash SetText("/");
|
||||
|
||||
max_hp = scripts\sp\tws_t5_common::make_text_elem(
|
||||
self, level.hud_col_health + 34, level.hud_row_bar + 3,
|
||||
"left", level.hud_font_mid, level.hud_c_high);
|
||||
max_hp SetValue(self.maxhealth);
|
||||
|
||||
cur.alpha = 0;
|
||||
slash.alpha = 0;
|
||||
max_hp.alpha = 0;
|
||||
|
||||
elems = [];
|
||||
elems[elems.size] = back;
|
||||
elems[elems.size] = bar;
|
||||
elems[elems.size] = cur;
|
||||
elems[elems.size] = slash;
|
||||
elems[elems.size] = max_hp;
|
||||
self thread scripts\sp\tws_t5_common::destroy_on_disconnect(elems);
|
||||
|
||||
last_width = bar_width;
|
||||
bar = self.tws_hud_bar;
|
||||
cur = self.tws_hud_health;
|
||||
slash = self.tws_hud_slash;
|
||||
max_hp = self.tws_hud_maxhealth;
|
||||
last_width = -1;
|
||||
last_health = -1;
|
||||
last_max = -1;
|
||||
last_shown = -1;
|
||||
last_max = -1;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (!isDefined(self.maxhealth) || self.maxhealth <= 0)
|
||||
self.maxhealth = 100;
|
||||
|
||||
frac = self.health / self.maxhealth;
|
||||
// Display fallbacks must not alter gameplay health values.
|
||||
maximum = 100;
|
||||
if (isDefined(self.maxhealth) && self.maxhealth > 0)
|
||||
maximum = self.maxhealth;
|
||||
current = 0;
|
||||
if (isDefined(self.health))
|
||||
current = max(0, self.health);
|
||||
frac = current / (maximum * 1.0);
|
||||
|
||||
if (frac > 1)
|
||||
frac = 1;
|
||||
|
||||
if (frac < 0)
|
||||
frac = 0;
|
||||
|
||||
width = int(max(frac * bar_width, 1));
|
||||
|
||||
if (width != last_width)
|
||||
{
|
||||
bar SetShader("white", width, level.hud_bar_height);
|
||||
last_width = width;
|
||||
}
|
||||
|
||||
colour = level.hud_c_high;
|
||||
bar.alpha = 1;
|
||||
if (frac == 0)
|
||||
bar.alpha = 0;
|
||||
|
||||
colour = level.hud_c_high;
|
||||
if (frac <= 0.25)
|
||||
colour = level.hud_c_low;
|
||||
else if (frac <= 0.5)
|
||||
colour = level.hud_c_mid;
|
||||
|
||||
bar.color = colour;
|
||||
cur.color = colour;
|
||||
slash.color = colour;
|
||||
bar.color = colour;
|
||||
cur.color = colour;
|
||||
slash.color = colour;
|
||||
max_hp.color = colour;
|
||||
|
||||
// numbers only show once you're hurt
|
||||
show = 0;
|
||||
|
||||
if (frac < level.hud_health_threshold)
|
||||
show = 1;
|
||||
|
||||
if (show != last_shown)
|
||||
// Health numbers remain visible at full health.
|
||||
if (current != last_health)
|
||||
{
|
||||
cur.alpha = show;
|
||||
slash.alpha = show;
|
||||
max_hp.alpha = show;
|
||||
last_shown = show;
|
||||
cur SetValue(current);
|
||||
last_health = current;
|
||||
}
|
||||
|
||||
if (self.health != last_health)
|
||||
if (maximum != last_max)
|
||||
{
|
||||
cur SetValue(self.health);
|
||||
last_health = self.health;
|
||||
}
|
||||
|
||||
if (self.maxhealth != last_max)
|
||||
{
|
||||
max_hp SetValue(self.maxhealth);
|
||||
last_max = self.maxhealth;
|
||||
max_hp SetValue(maximum);
|
||||
last_max = maximum;
|
||||
}
|
||||
|
||||
wait (level.hud_rate);
|
||||
|
||||
Reference in New Issue
Block a user