338 lines
9.8 KiB
Plaintext
338 lines
9.8 KiB
Plaintext
/* tws_t5_hud.gsc - minimal edge HUD, toggleable with .hud. */
|
|
|
|
#include maps\_utility;
|
|
#include common_scripts\utility;
|
|
|
|
init()
|
|
{
|
|
if (GetDvar(#"zombiemode") != "1")
|
|
return;
|
|
|
|
scripts\sp\tws_t5_common::ensure_config();
|
|
scripts\sp\tws_t5_common::register_help_command(".hud", "Toggle core HUD");
|
|
|
|
level thread round_watcher();
|
|
level thread game_over_watcher();
|
|
level thread hud_chat_listener();
|
|
|
|
scripts\sp\tws_t5_common::register_connect_handler(::hud_connect);
|
|
scripts\sp\tws_t5_common::register_spawn_handler(::hud_start);
|
|
}
|
|
|
|
hud_chat_listener()
|
|
{
|
|
for (;;)
|
|
{
|
|
level waittill("say", message, player);
|
|
if (!isDefined(player) || !isDefined(message))
|
|
continue;
|
|
if (toLower(message) == ".hud")
|
|
player thread hud_toggle();
|
|
}
|
|
}
|
|
|
|
hud_toggle()
|
|
{
|
|
self hud_connect();
|
|
self scripts\sp\tws_t5_common::toggle_core_hud();
|
|
}
|
|
|
|
// 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");
|
|
|
|
if (isDefined(self.tws_hud_elems))
|
|
return;
|
|
|
|
self.tws_hud_elems = [];
|
|
self.tws_hud_match = self hud_text(level.hud_overlay_timer_x,
|
|
level.hud_overlay_match_y, "left", 1.4, level.hud_c_count, "--:--");
|
|
self.tws_hud_round = self hud_text(level.hud_overlay_timer_x,
|
|
level.hud_overlay_round_y, "left", 1.25, level.hud_c_dim, "--:--");
|
|
self hud_text(level.hud_overlay_brand_x, level.hud_overlay_brand_y,
|
|
"center", 1.1, level.hud_c_dim, "TWS ZOMBIES · discord.tws.lol");
|
|
|
|
self.tws_hud_count = self hud_text(level.hud_overlay_count_x,
|
|
level.hud_overlay_count_y, "right", 1.5, level.hud_c_count, "--");
|
|
self hud_text(level.hud_overlay_count_label_x, level.hud_overlay_count_label_y,
|
|
"left", 1.05, level.hud_c_dim, "ZOMBIES");
|
|
|
|
// Independent numeric elements avoid a new config string on every tick.
|
|
// The readout appears only after a valid damaged-health sample.
|
|
self.tws_hud_health = self hud_text(level.hud_overlay_health_x,
|
|
level.hud_overlay_health_y, "right", 1.0, level.hud_c_count, "--");
|
|
self.tws_hud_slash = self hud_text(level.hud_overlay_slash_x,
|
|
level.hud_overlay_health_y, "left", 1.0, level.hud_c_count, "/");
|
|
self.tws_hud_maxhealth = self hud_text(level.hud_overlay_maxhealth_x,
|
|
level.hud_overlay_health_y, "right", 1.0, level.hud_c_count, "--");
|
|
self.tws_hud_hp = self hud_text(level.hud_overlay_hp_x,
|
|
level.hud_overlay_health_y, "left", 1.0, level.hud_c_count, "HP");
|
|
scripts\sp\tws_t5_common::set_core_alpha(self, self.tws_hud_health, 0);
|
|
scripts\sp\tws_t5_common::set_core_alpha(self, self.tws_hud_slash, 0);
|
|
scripts\sp\tws_t5_common::set_core_alpha(self, self.tws_hud_maxhealth, 0);
|
|
scripts\sp\tws_t5_common::set_core_alpha(self, self.tws_hud_hp, 0);
|
|
|
|
track = scripts\sp\tws_t5_common::make_core_overlay_shader(
|
|
self, level.hud_overlay_bar_x, level.hud_overlay_bar_y,
|
|
level.hud_overlay_bar_width, level.hud_overlay_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_core_overlay_shader(
|
|
self, level.hud_overlay_bar_x, level.hud_overlay_bar_y,
|
|
level.hud_overlay_bar_width, level.hud_overlay_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_core_overlay_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();
|
|
}
|
|
|
|
// Race the map-dependent game-over notifications once at level scope.
|
|
game_over_watcher()
|
|
{
|
|
level thread game_over_via("end_game");
|
|
level thread game_over_via("game_ended");
|
|
level thread game_over_via("intermission");
|
|
}
|
|
|
|
game_over_via(notify_name)
|
|
{
|
|
level endon("hud_game_over");
|
|
level waittill(notify_name);
|
|
level.tws_ended = true;
|
|
level notify("hud_game_over");
|
|
}
|
|
|
|
// 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))
|
|
last = level.round_number;
|
|
|
|
for (;;)
|
|
{
|
|
if (isDefined(level.round_number) && level.round_number != last)
|
|
{
|
|
last = level.round_number;
|
|
level.round_start_time = getTime();
|
|
|
|
if (!isDefined(level.match_start_time) && level.round_number >= 1)
|
|
{
|
|
level.match_start_time = getTime();
|
|
level notify("hud_match_started");
|
|
}
|
|
|
|
level notify("hud_round_changed");
|
|
}
|
|
|
|
wait (0.5);
|
|
}
|
|
}
|
|
|
|
hud_timer_row()
|
|
{
|
|
self endon("disconnect");
|
|
|
|
// A visible HUD 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)
|
|
{
|
|
level.match_start_time = getTime();
|
|
level.round_start_time = getTime();
|
|
level notify("hud_match_started");
|
|
}
|
|
|
|
round = self.tws_hud_round;
|
|
round SetTimerUp(0);
|
|
|
|
self thread reset_timer_each_round(round);
|
|
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 (;;)
|
|
{
|
|
level waittill("hud_round_changed");
|
|
|
|
if (isDefined(timer))
|
|
timer SetTimerUp(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);
|
|
}
|
|
|
|
// Preserve authoritative frozen clocks when the core is shown after game over.
|
|
freeze_timers_on_game_over(match_elem, round_elem)
|
|
{
|
|
self endon("disconnect");
|
|
level waittill("hud_game_over");
|
|
|
|
if (isDefined(match_elem) && isDefined(level.match_start_time))
|
|
match_elem SetText(scripts\sp\tws_t5_common::format_mmss(getTime() - level.match_start_time));
|
|
|
|
if (isDefined(round_elem) && isDefined(level.round_start_time))
|
|
round_elem SetText(scripts\sp\tws_t5_common::format_mmss(getTime() - level.round_start_time));
|
|
}
|
|
|
|
hud_zombie_counter()
|
|
{
|
|
self endon("disconnect");
|
|
level endon("hud_game_over");
|
|
|
|
count = self.tws_hud_count;
|
|
last = -1;
|
|
|
|
for (;;)
|
|
{
|
|
remaining = get_enemy_count();
|
|
if (isDefined(level.zombie_total))
|
|
remaining += max(0, level.zombie_total);
|
|
|
|
if (remaining != last)
|
|
{
|
|
count SetValue(remaining);
|
|
last = remaining;
|
|
}
|
|
|
|
wait (level.hud_rate);
|
|
}
|
|
}
|
|
|
|
get_enemy_count()
|
|
{
|
|
valid = [];
|
|
enemies = GetAiSpeciesArray("axis", "all");
|
|
|
|
for (i = 0; i < enemies.size; i++)
|
|
{
|
|
if (is_true(enemies[i].ignore_enemy_count))
|
|
continue;
|
|
|
|
if (isDefined(enemies[i].animname))
|
|
valid[valid.size] = enemies[i];
|
|
}
|
|
|
|
return valid.size;
|
|
}
|
|
|
|
hud_health_display()
|
|
{
|
|
self endon("disconnect");
|
|
level endon("hud_game_over");
|
|
|
|
bar_width = level.hud_overlay_bar_width;
|
|
bar = self.tws_hud_bar;
|
|
cur = self.tws_hud_health;
|
|
slash = self.tws_hud_slash;
|
|
max_hp = self.tws_hud_maxhealth;
|
|
hp = self.tws_hud_hp;
|
|
last_width = -1;
|
|
last_health = -1;
|
|
last_max = -1;
|
|
|
|
for (;;)
|
|
{
|
|
// 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;
|
|
|
|
width = scripts\sp\tws_t5_common::make_overlay_width(frac * bar_width);
|
|
if (width != last_width)
|
|
{
|
|
bar SetShader("white", width, level.hud_overlay_bar_height);
|
|
last_width = width;
|
|
}
|
|
|
|
bar_alpha = 1;
|
|
if (frac == 0)
|
|
bar_alpha = 0;
|
|
scripts\sp\tws_t5_common::set_core_alpha(self, bar, bar_alpha);
|
|
|
|
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;
|
|
max_hp.color = colour;
|
|
hp.color = colour;
|
|
|
|
readout_alpha = 0;
|
|
if (isDefined(self.health) && current < maximum)
|
|
readout_alpha = 1;
|
|
scripts\sp\tws_t5_common::set_core_alpha(self, cur, readout_alpha);
|
|
scripts\sp\tws_t5_common::set_core_alpha(self, slash, readout_alpha);
|
|
scripts\sp\tws_t5_common::set_core_alpha(self, max_hp, readout_alpha);
|
|
scripts\sp\tws_t5_common::set_core_alpha(self, hp, readout_alpha);
|
|
|
|
// Keep health numbers current even at full health or while hidden.
|
|
if (current != last_health)
|
|
{
|
|
cur SetValue(current);
|
|
last_health = current;
|
|
}
|
|
|
|
if (maximum != last_max)
|
|
{
|
|
max_hp SetValue(maximum);
|
|
last_max = maximum;
|
|
}
|
|
|
|
wait (level.hud_rate);
|
|
}
|
|
}
|