/* tws_t6_hud.gsc - configurable HUD stack: timers, zombie count, health bar. */ #include maps\mp\_utility; #include common_scripts\utility; // Plutonium probes both entry points; runtime setup belongs in init. main() { } init() { scripts\zm\tws_t6_common::ensure_config(); level thread round_watcher(); level thread game_over_watcher(); scripts\zm\tws_t6_common::register_spawn_handler(::hud_start); } hud_start() { self endon("disconnect"); if (isDefined(level.tws_ended) && level.tws_ended) return; self thread hud_timer_row(); self thread hud_zombie_counter(); self thread hud_health_display(); } // Same game-over notification fan-out as T5. 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"); } // Poll stock round state; the first player spawn seeds pre-set round 1. 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"); // fallback seed for the round-1-already-active case (round_watcher misses it) 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"); } match = scripts\zm\tws_t6_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\zm\tws_t6_common::make_text_elem( self, level.hud_x, level.hud_row_round, "left", level.hud_font_small, level.hud_c_dim); round SetTimerUp(0); if (isDefined(level.round_start_time)) round SetTimerUp(max(0, (getTime() - level.round_start_time) / 1000.0)); // name only shows in co-op — your own name is noise in solo name = scripts\zm\tws_t6_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\zm\tws_t6_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; players = getPlayers(); if (players.size > 1) is_coop = 1; if (is_coop != coop) { name.alpha = is_coop; coop = is_coop; } wait (1); } } 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); } } // T6 timer seed includes elapsed time for late joiners. match_timer_bind(elem) { self endon("disconnect"); if (!isDefined(level.match_start_time)) level waittill("hud_match_started"); if (isDefined(elem)) { elem SetTimerUp(max(0, (getTime() - level.match_start_time) / 1000.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. */ 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\zm\tws_t6_common::format_mmss(getTime() - level.match_start_time)); if (isDefined(round_elem) && isDefined(level.round_start_time)) round_elem SetText(scripts\zm\tws_t6_common::format_mmss(getTime() - level.round_start_time)); } hud_zombie_counter() { self endon("disconnect"); label = scripts\zm\tws_t6_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\zm\tws_t6_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\zm\tws_t6_common::destroy_on_disconnect(elems); last = -1; for (;;) { remaining = 0; if (isDefined(level.zombie_team)) remaining = maps\mp\zombies\_zm_utility::get_current_zombie_count(); if (isDefined(level.zombie_total)) remaining += max(0, level.zombie_total); if (remaining != last) { count SetValue(remaining); last = remaining; } wait (level.hud_rate); } } hud_health_display() { self endon("disconnect"); bar_width = level.hud_bar_width; // -2 on both axes so the back frames the bar with 2px padding back = scripts\zm\tws_t6_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("white", bar_width + 4, level.hud_bar_height + 4); bar = scripts\zm\tws_t6_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\zm\tws_t6_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\zm\tws_t6_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\zm\tws_t6_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(100); 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\zm\tws_t6_common::destroy_on_disconnect(elems); last_width = bar_width; last_health = -1; last_max = -1; last_shown = -1; for (;;) { maximum = 100; if (isDefined(self.maxhealth) && self.maxhealth > 0) maximum = self.maxhealth; frac = self.health / (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; 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; // numbers only show once you're hurt show = 0; if (frac < level.hud_health_threshold) show = 1; if (show != last_shown) { cur.alpha = show; slash.alpha = show; max_hp.alpha = show; last_shown = show; } if (self.health != last_health) { cur SetValue(self.health); last_health = self.health; } if (maximum != last_max) { max_hp SetValue(maximum); last_max = maximum; } wait (level.hud_rate); } }