Simplify T5 and T6 HUD overlays and service displays

This commit is contained in:
2026-10-15 21:07:10 -04:00
parent 929bf0354b
commit 2b5b1955bc
15 changed files with 1934 additions and 643 deletions
+49 -18
View File
@@ -35,8 +35,9 @@ before copying the renamed scripts.
- **Dynamic numbers use `SetValue`.** Repeatedly generating unique strings
risks exhausting the text config-string pool; identical strings can be reused
by the engine's [string lookup](https://github.com/MisaDev4/bo1-zombies-decompiled/blob/102f6992be332610aff587e681e3180b15f395bb/src/game_mp/g_utils_mp.cpp#L46).
Composite HP uses numeric elements around a static slash. Help updates two
multiline columns only on page changes, and record rows skip unchanged text.
Composite HP uses numeric elements around a static slash and `HP` suffix;
the group appears only while injured. Help reuses up to nine explicitly
positioned command/description pairs, and record rows skip unchanged text.
- **`NewClientHudElem(player)`, not `NewHudElem()`.** `NewHudElem` is
globally owned; in co-op every player's HUD draws on top of every other
player's. Fall back to `NewHudElem` only if elements don't render at all
@@ -58,21 +59,39 @@ before copying the renamed scripts.
Many BO1 maps pre-set `round_number = 1` at init, so the round watcher
fires on transitions only and the timer row seeds on first spawn as a
fallback.
- **Connection-time HUD creation must not seed clocks.** The upper-right core
is available to waiting spectators with placeholders. Timer updates retain
- **Connection-time HUD creation must not seed clocks.** The unboxed core is
available to waiting spectators with placeholders: timers upper left,
branding top center and zombies/health bottom center. Timer updates retain
their existing spawn/round lifecycle, independently of element creation.
- **One service window per player.** Common owns fixed geometry, ordered
four-command help pages, record display state and cleanup. Help pauses record
page time; `.leaders` during help replaces the pending replay from page one.
Keep both services independent of the HUD module. The core never moves or
resizes when a service opens, and the AFK indicator uses its reserved footer.
- **Visibility is separate from updates.** HUD owns the exact, case-insensitive
`.hud` chat command and its help entry. Common's core wrappers register each
element and preserve its intended alpha behind a per-player visibility mask;
health and AFK changes use `set_core_alpha()`. Hidden data/timers keep updating
until game over freezes them. Help, records and stock elements remain separate.
Visibility defaults on, survives death/respawn during the connection/map, and
resets on reconnect or map restart. AFK protection is unaffected.
- **One service view per player.** Common owns the unboxed upper-right service
area, ordered help rows, record display state and cleanup. Help shows all
nine installed commands; extra registrations use nine rows per page. Help
pauses record page time; `.leaders` during help replaces the pending replay
from page one. Keep both services independent of the HUD module. Services
never reposition the core, and AFK remains centered above the zombie count.
- **Bound HUD element counts, including hidden elements.** Core + AFK use
15 + 1 archived elements; the service uses 19 nonarchived elements regardless
of command/page count. Help reuses two multiline columns and one muted column
backdrop. Hiding an element does not free its slot. The
11 + 1 archived elements. Common allocates only the active service view:
19 nonarchived elements for nine-command help (20 with a multi-page counter),
or four for records (five with a multi-page counter), and none while idle.
Switching help/record views destroys the previous view first; pages reuse
its elements. Keep the controller and pending requests separate from element
lifetime so paused records resume normally. Both engines use explicit help
rows; BO1 can concatenate newline-separated HUD text. Hiding an element does
not free its slot. The previous 24-element persistent service allocation was
consistent with the reported core corruption after records first appeared.
The exact live T5 SP capacity remains unverified. The
[BO1 HUD update](https://github.com/MisaDev4/bo1-zombies-decompiled/blob/102f6992be332610aff587e681e3180b15f395bb/src/game/g_hudelem.cpp#L2596)
shows 31 slots per bucket; this is not proof of current Plutonium stock HUD
usage, which still needs live validation.
comes from an MP-based reconstruction and is not proof of T5 SP limits or
current Plutonium stock usage. Validate automatic records, help, view switches
and dismissal alongside stock notifications and other installed HUD mods;
the core must remain intact throughout and after every service closes.
- **Game-over trigger is level-scope, raced across three candidate
notifies** (`end_game` / `game_ended` / `intermission`) since BO1 maps
are inconsistent. Per-player triggers do not work — `self waittill("death")`
@@ -80,9 +99,21 @@ before copying the renamed scripts.
polling `self.health` misses the zero-crossing (health is rewritten during
the game-over sequence).
HUD `1.2.0`, leaderboard `1.1.0` and AFK `1.0.1` require matching common `1.2.0+`.
Install the changed scripts together and restart the map. There is no new
native-plugin/backend dependency or full server-process restart requirement.
HUD `1.4.0` and AFK `1.2.0` require matching common `1.4.0+`. Update all three
where installed and restart the map. Leaderboard `1.1.0` remains compatible.
The full help order is `.help`, `.hud`, `.afk`, `.back`, `.join`, `.leaders`,
`.b`, `.d`, `.w`: one page of nine commands, with full `<amount|all>` bank
syntax. `.hud` is absent when HUD is omitted. Records use right-aligned
`Map record · Round N` and up to three bounded holder lines, with no footer
and no single-page counter. Both views retain 12-second page timing.
Fullscreen X/Y overlay anchors use direct normalized `640 × 480` coordinates.
Dynamic health fills use `make_overlay_width()` like their tracks. Apply
`int()` after `max()` in that helper: native `max()` returns a float, but
`SetShader` requires an integer width. Legacy panel helpers and their `0.75`
horizontal conversion remain available for existing consumers. Font scale is
at least `1.0`. Numeric HP appears only while injured; the core and services
have no panel backgrounds. There is no new Plutonium/native-plugin/backend
dependency or full server-process restart requirement.
Run the [shared HUD checks](../../README.md#hud-validation); portable fixtures
do not replace T5 live loading, layout and lifecycle validation.
@@ -91,7 +122,7 @@ do not replace T5 live loading, layout and lifecycle validation.
| File | Role | Fires notifies | Reads notifies |
|---|---|---|---|
| `tws_t5_common` | Shared config, HUD helpers, paginated `.help`, service window, spawn-handler registry, single connect listener + dispatcher | — | `say`, game-end notifications |
| `tws_t5_hud` | Upper-right core: branding, timer row, zombie count, HP and health bar | `hud_round_changed`, `hud_match_started`, `hud_game_over` | (internal) |
| `tws_t5_hud` | Unboxed core overlays and private `.hud` visibility toggle | `hud_round_changed`, `hud_match_started`, `hud_game_over` | `say`, internal notifications |
| `tws_t5_afk` | `.afk` / `.back` chat toggle, owns its [AFK] indicator | — | — |
| `tws_t5_join` | `.join` for spectators who have not played on this connection | — | `say`, `end_game`, `game_ended`, `intermission` |
| `tws_t5_lock` | Lobby `g_password` lock at milestone round (`tws_lock_round`, default 20) | — | `hud_round_changed`, `hud_game_over` |
+90 -42
View File
@@ -11,7 +11,7 @@ Platform quirks and deploy commands: [NOTES.md](NOTES.md).
| File | Role |
|---|---|
| [tws_t5_common.gsc](tws_t5_common.gsc) | Shared config, HUD helpers, paginated `.help`, service window, connect/spawn registries, single `connected` listener + dispatcher |
| [tws_t5_hud.gsc](tws_t5_hud.gsc) | Fixed upper-right core: branding, timers, zombie count, current/maximum HP and health bar |
| [tws_t5_hud.gsc](tws_t5_hud.gsc) | Unboxed timers, branding, zombies/health and per-player `.hud` visibility toggle |
| [tws_t5_afk.gsc](tws_t5_afk.gsc) | `.afk` / `.back` chat toggle + `[AFK]` indicator |
| [tws_t5_join.gsc](tws_t5_join.gsc) | `.join` lets newcomers spawn before the current round ends |
| [tws_t5_lock.gsc](tws_t5_lock.gsc) | Lobby `g_password` lock at a milestone round |
@@ -56,22 +56,43 @@ invalid identities fail privately without changing points. Follow the shared
## Core HUD and service window
The fixed upper-right core shows `TWS ZOMBIES`, `discord.tws.lol`, side-by-side
match/round timers, zombie count, current/maximum HP and a full-width health bar.
HP stays visible at full health. A reserved footer holds the AFK indicator;
there is no co-op name row. The core is created once per connection, including
for spectator newcomers with placeholders until values are available. Creation
does not seed the match clock. It persists across death and respawn.
Clocks, health and zombie-count displays freeze at game over. Health fallbacks
only affect the display and never modify gameplay max health.
The unboxed core puts match time above round time at the upper left,
`TWS ZOMBIES · discord.tws.lol` at the top center, and zombies remaining above a
slim health bar at the bottom center. Current/maximum HP appears to the right
of the bar only while injured; it disappears at full health. The AFK indicator
sits above the zombie count. There are no panel backgrounds or timer labels.
Fullscreen X/Y anchors use the normalized `640 × 480` viewport directly and
bypass safe-area insets. Font scale is at least `1.0`; verify text fit, stock
HUD overlap and Plutonium tag clearance at each aspect ratio in-game.
Common provides one fixed-height service window beneath the core for help or
records. Services never move or resize the core. They remain private and work
without the HUD module installed. See the [shared layout and validation guide](../../README.md#core-hud-and-service-window-t5-and-t6).
The core is created once per connection, including for spectator newcomers
with placeholders until values are available. Creation does not seed the match
clock. It persists across death and respawn. Clocks, health and zombie-count
displays freeze at game over. Health fallbacks only affect the display and
never modify gameplay max health.
Install HUD `1.2.0`, leaderboard `1.1.0` and AFK `1.0.1` with common `1.2.0+`.
Update the installed scripts together and restart the map; this change requires
no new plugin/backend dependency or full server-process restart.
The exact, case-insensitive `.hud` command toggles your core and AFK indicator.
It defaults visible, keeps your choice across death/respawn on the current
connection/map, and resets visible on reconnect or map restart. Timers and data
continue updating while hidden; game-over values remain frozen. Help, records
and the stock HUD are unaffected. `.hud` requires the HUD module.
Common provides one unboxed upper-right service area below the Plutonium tag.
Records are right aligned, with `Map record · Round N` followed by up to three
holder lines; there is no replay footer or single-page counter. Help displays
the full nine-command list in aligned rows. Services remain private, leave the
core in place and work without the HUD module installed. See the
[shared layout and validation guide](../../README.md#core-hud-and-service-window-t5-and-t6).
Only the active service view holds HUD elements: 19 for nine-command help
(20 with a multi-page counter), or four for records (five with a multi-page
counter). Pages reuse elements; switching views or closing the service releases
them. Idle services hold no HUD elements, while the controller retains pending
requests and interruption/resume behavior. Core + AFK use 11 + 1 elements.
HUD `1.4.0` and AFK `1.2.0` require common `1.4.0+`. Update common, HUD and AFK
together where installed, then restart the map. Leaderboard `1.1.0` remains
compatible. This change requires no new Plutonium/plugin/backend dependency or
full server-process restart.
## Map record announcements
@@ -93,14 +114,17 @@ apply to both games.
Type `.help` or `.commands` in chat to see available player commands and a short description.
The response is visible only to the requesting player and works while spectating,
before the first spawn. Commands are case-insensitive.
Help uses the service window below the core. It shows four commands per page,
advances automatically every 12 seconds and closes after the last page. The
panel retains its height on short pages. Typing either command again returns to
page one with a fresh timer.
Help uses the unboxed upper-right service area. All nine installed commands
fit in one list with command and description columns. Additional registered
commands use pages of nine rows, with a counter only for multi-page lists.
Pages advance every 12 seconds and close after the last page. Typing either
command again returns to page one with a fresh timer. Both engines use
explicit row positions so T5 does not concatenate newline-separated text.
| Command | Effect |
|---|---|
| `.help`, `.commands` | List available commands |
| `.hud` | Hide or show your core HUD and AFK indicator (HUD module required) |
| `.afk` | Pause with protection |
| `.back` | Return from AFK |
| `.join` | Join mid-round (newcomers only) |
@@ -112,6 +136,9 @@ page one with a fresh timer.
Each module registers its own help entries. Commands follow the order above,
independent of initialization order; removing a module also removes its entries
from `.help` after a map restart.
With all command modules installed, help shows one page of nine commands,
including the full `.d <amount|all>` / `.w <amount|all>` syntax. Removing HUD
removes `.hud` from the list.
The enabled leaderboard module registers `.leaders`, which replays the latest
cached map record privately. Repeating it restarts the record from the first page;
help pauses the record's remaining page time and resumes it after the final
@@ -170,13 +197,15 @@ single `level waittill("connected")` in the fleet. Adding a second
connect listener anywhere else crashes the T5 SP handshake.
**Features**
- Shared HUD constants for panel geometry, rows/columns, fonts, health bar,
- Shared HUD constants for overlay positions, rows/columns, fonts, health bar,
colors and update rate
- Spawn-handler registry
- Connection-handler registry for optional modules that serve spectator newcomers
- `.help` / `.commands` listener and ordered help registry populated by installed modules
- One reusable service window per player, with help/record pagination,
interruption/resume, request replacement and disconnect/game-end cleanup
- One service controller per player, with help/record pagination,
interruption/resume, request replacement and disconnect/game-end cleanup;
display elements exist only for the active view and are released when idle
- Core element registry with per-element alpha state and a per-player visibility mask
- Connect listener + per-player dispatch: iterates
`level.tws_spawn_handlers` after `spawned_player` and threads each on
the player
@@ -191,8 +220,18 @@ connect listener anywhere else crashes the T5 SP handshake.
| `register_help_command(command, description)` | Adds a player command to `.help`; call from the owning module's `init()` |
| `make_text_elem(p, x, y, ax, s, c)`| `NewClientHudElem` text element with black-glow outline |
| `make_shader_elem(p, x, y)` | `NewClientHudElem` shader element |
| `make_panel_text(p, x, y, ax, s, c)` | Text positioned relative to the fixed upper-right core origin |
| `make_panel_shader(p, x, y, w, h, c, alpha, sort)` | Background/bar shader positioned relative to the core origin |
| `make_overlay_text(p, x, y, ax, s, c)` | Text at absolute fullscreen coordinates in the normalized `640 × 480` frame |
| `make_overlay_shader(p, x, y, w, h, c, alpha, sort)` | Shader at absolute fullscreen coordinates |
| `make_overlay_width(width)` | Clamp a normalized width and return the integer required by `SetShader` |
| `make_core_overlay_text(p, x, y, ax, s, c)` | Create overlay text registered for `.hud` visibility |
| `make_core_overlay_shader(p, x, y, w, h, c, alpha, sort)` | Create an overlay shader registered for `.hud` visibility |
| `make_panel_text(p, x, y, ax, s, c)` | Legacy text positioned relative to the former upper-right panel origin |
| `make_panel_shader(p, x, y, w, h, c, alpha, sort)` | Legacy shader positioned relative to that panel origin |
| `make_panel_width(width)` | Retained legacy design-width conversion for existing consumers |
| `make_core_text(p, x, y, ax, s, c)` | Retained legacy panel text with `.hud` visibility |
| `make_core_shader(p, x, y, w, h, c, alpha, sort)` | Retained legacy panel shader with `.hud` visibility |
| `set_core_alpha(p, elem, alpha)` | Preserve the element's intended alpha while applying the player's visibility choice |
| `toggle_core_hud()` | Toggle the player's registered core elements without stopping updates |
| `show_record(snapshot)` | Replace the player's record service request; an undefined snapshot shows unavailable |
| `destroy_on_disconnect(elems)` | Waits on `disconnect`, destroys the array |
| `int_dvar_or(name, fallback)` | Parse an integer dvar with default |
@@ -203,31 +242,39 @@ connect listener anywhere else crashes the T5 SP handshake.
**Live validation (requires a T5 server)**
1. Type `.help`, `.HELP` and `.commands` while playing and spectating before the
first spawn. Verify four commands per page in the service window, private to
the requesting player. Check that full and damaged HP remain visible above it.
first spawn. Verify all nine commands appear in one unboxed list, private to
the requesting player. Check explicit rows, full bank syntax and the absence
of a single-page counter. HP should appear beside the bottom health bar only
while injured.
2. Remove AFK or join, restart the map, and verify its entries disappear while
`.help` and the remaining commands still work. Restore the scripts afterward.
3. Verify pages advance every 12 seconds and close after the last page. Repeat
`.help` on a later page and check that it restarts page one. Interrupt a record
3. Verify help closes after 12 seconds. Add custom registered commands to test
the nine-row page limit, counter and repeat-to-restart behavior. Interrupt a record
with help and verify remaining page time resumes; repeat `.leaders` during
help and verify only the latest replay starts from page one.
4. Reopen after expiry, die/respawn and reconnect; verify no duplicate text or
leftover elements. Repeat without the HUD module. Follow the complete
[HUD validation guide](../../README.md#hud-validation) before rollout.
5. Watch the first automatic record announcement and its dismissal. Repeat
records, help and interrupted/resumed records alongside stock notifications.
Check that branding, the health bar, damage-only HP, zombie count and timers
remain correct during each view and after it closes.
Include other installed HUD mods; the live T5 SP element limit is unverified.
---
## tws_t5_hud.gsc
Fixed upper-right core panel. Per-player elements are created on connection;
Unboxed core overlays. Per-player elements are created on connection;
gameplay updates start through the existing spawn lifecycle.
**Features**
- Match timer + round timer (round resets each round, both freeze on game over)
- `TWS ZOMBIES` / `discord.tws.lol` header and reserved AFK footer
- Zombies-remaining counter
- Full-width health bar with green / amber / red ramp; current/maximum HP
remains visible at full health
- Upper-left match timer above round timer (round resets each round, both freeze on game over)
- `.hud` toggles this player's core and AFK indicator; data updates continue while hidden
- Top-center `TWS ZOMBIES · discord.tws.lol` branding
- Bottom-center zombies-remaining counter, with the AFK indicator above it
- Slim health bar with green / amber / red ramp; current/maximum HP appears
beside it only while injured
- Spectator placeholders without starting the match clock on connection
- Round-transition and game-over notify machinery
@@ -246,8 +293,9 @@ Connection handler registered: `::hud_connect` — creates the core with placeho
without seeding clocks; `hud_start` calls it as a fallback if needed.
**Depends on**
- `scripts\sp\tws_t5_common::` — `ensure_config`, `make_panel_text`,
`make_panel_shader`, `destroy_on_disconnect`, `format_mmss`,
- `scripts\sp\tws_t5_common::` — `ensure_config`, `make_core_overlay_text`,
`make_core_overlay_shader`, `make_overlay_width`, `set_core_alpha`, `toggle_core_hud`,
`destroy_on_disconnect`, `format_mmss`, `register_help_command`,
`register_spawn_handler`, `register_connect_handler`
- Every `level.hud_*` config value from `tws_t5_common`
- Stock BO1 state: `level.round_number`, `level.zombie_total`,
@@ -258,8 +306,8 @@ without seeding clocks; `hud_start` calls it as a fallback if needed.
## tws_t5_afk.gsc
Self-invoked pause mode. Owns the `[AFK]` indicator element, positioned in the
core's reserved footer through common layout values. It does not require HUD.
Self-invoked pause mode. Owns the `[AFK]` indicator element, centered above the
zombie count through common layout values. It does not require HUD.
**Features**
- Chat commands `.afk` and `.back` (via `level waittill("say")` — not
@@ -269,7 +317,7 @@ core's reserved footer through common layout values. It does not require HUD.
- On exit: reverse, then a 2s grace tail keeps invuln + ignoreme through
the target-reacquisition window
- `[AFK]` HUD element created per-player via spawn handler; alpha-toggled
by enter / exit
by enter / exit through common's core visibility helpers, respecting `.hud`
**Provides**
- Per-player: `self.hud_afk_on` (bool), `self.hud_afk_elem` (HUD element),
@@ -277,9 +325,9 @@ core's reserved footer through common layout values. It does not require HUD.
- Level var: `level.afk_grace_seconds` (default 2)
**Depends on**
- `scripts\sp\tws_t5_common::` — `ensure_config`, `make_panel_text`,
`destroy_on_disconnect`, `register_spawn_handler`, `register_help_command`
- Shared config: core footer position, `level.hud_font_small`, `level.hud_c_afk`
- `scripts\sp\tws_t5_common::` — `ensure_config`, `make_core_overlay_text`,
`set_core_alpha`, `destroy_on_disconnect`, `register_spawn_handler`, `register_help_command`
- Shared config: overlay AFK position, `level.hud_font_afk`, `level.hud_c_afk`
- Stock BO1: `enableInvulnerability`, `disableInvulnerability`,
`freezeControls`, `.ignoreme`, `level waittill("say", message, player)`
+6 -6
View File
@@ -50,11 +50,11 @@ afk_spawn()
{
self endon("disconnect");
afk = scripts\sp\tws_t5_common::make_panel_text(
self, level.hud_pad, level.hud_row_afk,
"left", level.hud_font_afk, level.hud_c_afk);
afk = scripts\sp\tws_t5_common::make_core_overlay_text(
self, level.hud_overlay_afk_x, level.hud_overlay_afk_y,
"center", 1.0, level.hud_c_afk);
afk SetText("[AFK]");
afk.alpha = 0;
scripts\sp\tws_t5_common::set_core_alpha(self, afk, 0);
self.hud_afk_elem = afk;
elems = [];
@@ -75,7 +75,7 @@ afk_enter()
self freezeControls(true);
if (isDefined(self.hud_afk_elem))
self.hud_afk_elem.alpha = 1;
scripts\sp\tws_t5_common::set_core_alpha(self, self.hud_afk_elem, 1);
self iPrintLnBold("AFK — type .back to return");
}
@@ -91,7 +91,7 @@ afk_exit()
self freezeControls(false);
if (isDefined(self.hud_afk_elem))
self.hud_afk_elem.alpha = 0;
scripts\sp\tws_t5_common::set_core_alpha(self, self.hud_afk_elem, 0);
self iPrintLnBold("Back in the fight");
+288 -123
View File
@@ -28,8 +28,7 @@ common_config()
if (isDefined(level.hud_x))
return;
// Existing generic helpers retain their screen-anchor conventions. Panels
// use their own right-edge origin and never push stock HUD areas around.
// Retain legacy panel coordinates for existing helper consumers.
level.hud_x = 10;
level.hud_y = 30;
// Retain the legacy fields for consumers of the original common config.
@@ -43,7 +42,10 @@ common_config()
level.hud_col_health = level.hud_x + 120 + 12;
level.hud_health_threshold = 1.0;
level.hud_panel_right = 10;
level.hud_panel_top = 42;
level.hud_panel_top = 20;
// Fullscreen uses a 640x480 frame. Convert the panel's 480-height units
// to that frame at the 16:9 reference aspect (640 / (480 * 16 / 9)).
level.hud_panel_x_scale = 0.75;
level.hud_panel_width = 220;
level.hud_core_height = 100;
level.hud_service_y = 108;
@@ -73,6 +75,38 @@ common_config()
level.hud_c_low = (1, 0.2, 0.2);
level.hud_c_afk = (1, 0.8, 0.2);
// Minimal overlays use absolute coordinates in the fullscreen 640x480 frame.
level.hud_overlay_timer_x = 6;
level.hud_overlay_match_y = 8;
level.hud_overlay_round_y = 30;
level.hud_overlay_brand_x = 320;
level.hud_overlay_brand_y = 8;
level.hud_overlay_count_x = 300;
level.hud_overlay_count_y = 440;
level.hud_overlay_count_label_x = 306;
level.hud_overlay_count_label_y = 446;
level.hud_overlay_bar_x = 242;
level.hud_overlay_bar_y = 466;
level.hud_overlay_bar_width = 156;
level.hud_overlay_bar_height = 4;
level.hud_overlay_health_y = 460;
level.hud_overlay_health_x = 414;
level.hud_overlay_slash_x = 418;
level.hud_overlay_maxhealth_x = 445;
level.hud_overlay_hp_x = 449;
level.hud_overlay_afk_x = 320;
level.hud_overlay_afk_y = 421;
level.hud_service_left = 432;
level.hud_service_right = 633;
level.hud_service_description_x = 538;
level.hud_service_title_y = 54;
level.hud_service_help_y = 80;
level.hud_service_help_pitch = 24;
level.hud_service_help_rows = 9;
level.hud_service_record_y = 36;
level.hud_service_holders_y = 60;
level.hud_service_holders_pitch = 18;
if (!isDefined(level.tws_spawn_handlers))
level.tws_spawn_handlers = [];
}
@@ -137,7 +171,7 @@ show_help()
state = self.tws_service;
service_advance(state, getTime());
state.generation++;
state.help = service_request("help", service_page_count(state.commands.size, 4), state.generation);
state.help = service_request("help", service_page_count(state.commands.size, level.hud_service_help_rows), state.generation);
service_render(state);
}
@@ -209,14 +243,15 @@ service_advance(state, now)
help_rank(command)
{
if (command == ".help") return 0;
if (command == ".afk") return 1;
if (command == ".back") return 2;
if (command == ".join") return 3;
if (command == ".leaders") return 4;
if (command == ".b") return 5;
if (command.size >= 3 && getSubStr(command, 0, 3) == ".d ") return 6;
if (command.size >= 3 && getSubStr(command, 0, 3) == ".w ") return 7;
return 8;
if (command == ".hud") return 1;
if (command == ".afk") return 2;
if (command == ".back") return 3;
if (command == ".join") return 4;
if (command == ".leaders") return 5;
if (command == ".b") return 6;
if (command.size >= 3 && getSubStr(command, 0, 3) == ".d ") return 7;
if (command.size >= 3 && getSubStr(command, 0, 3) == ".w ") return 8;
return 9;
}
help_entries()
@@ -224,7 +259,7 @@ help_entries()
entries = [];
if (!isDefined(level.tws_help_commands))
return entries;
for (rank = 0; rank <= 8; rank++)
for (rank = 0; rank <= 9; rank++)
{
for (i = 0; i < level.tws_help_commands.size; i++)
{
@@ -238,9 +273,6 @@ help_entries()
help_command_label(command)
{
rank = help_rank(command);
if (rank == 6) return ".d <n|all>";
if (rank == 7) return ".w <n|all>";
return command;
}
@@ -248,13 +280,14 @@ help_description(entry)
{
rank = help_rank(entry.command);
if (rank == 0) return "Show commands";
if (rank == 1) return "Enable protection";
if (rank == 2) return "Return to play";
if (rank == 3) return "Join as newcomer";
if (rank == 4) return "View records";
if (rank == 5) return "Check balance";
if (rank == 6) return "Deposit points";
if (rank == 7) return "Withdraw points";
if (rank == 1) return "Toggle core HUD";
if (rank == 2) return "Protect & freeze";
if (rank == 3) return "Return to play";
if (rank == 4) return "Join as newcomer";
if (rank == 5) return "View records";
if (rank == 6) return "Bank balance";
if (rank == 7) return "Deposit";
if (rank == 8) return "Withdraw";
return entry.description;
}
@@ -277,57 +310,82 @@ service_ensure()
state.record_text = [];
state.help_page = -1;
state.rows = [];
state.command_rows = [];
state.description_rows = [];
state.view_kind = undefined;
self.tws_service = state;
y = level.hud_service_y;
state.background = make_panel_shader(self, 0, y, level.hud_panel_width,
level.hud_service_height, level.hud_c_panel, level.hud_panel_alpha, 0);
state.elems[state.elems.size] = state.background;
state.help_title = service_text(self, state, 10, 10, "left", 1.1, "COMMANDS", level.hud_c_label);
state.record_title = service_text(self, state, 10, 10, "left", 1.1, "MAP RECORD", level.hud_c_label);
state.help_footer = service_text(self, state, 10, 108, "left", 0.9,
"Four commands per page", level.hud_c_dim);
state.record_footer = service_text(self, state, 10, 108, "left", 0.9,
"Briefly shown - .leaders to view again", level.hud_c_dim);
state.page_number = service_text(self, state, 178, 10, "right", 1.1, "", level.hud_c_dim);
state.page_slash = service_text(self, state, 184, 10, "center", 1.1, "/", level.hud_c_dim);
state.page_total = service_text(self, state, 210, 10, "right", 1.1, "", level.hud_c_dim);
// Two fixed text columns keep the HUD element budget independent of the
// command count. Strings change only at page boundaries, never per tick.
state.command_back = make_panel_shader(self, 10, y + 30, 65, 68,
(0.16, 0.19, 0.17), 0.9, 5);
state.elems[state.elems.size] = state.command_back;
state.command_column = service_text(self, state, 14, 31,
"left", 1.0, "", level.hud_c_name);
state.description_column = service_text(self, state, 81, 31,
"left", 1.0, "", level.hud_c_count);
state.round_label = service_text(self, state, 10, 32, "left", 1.1, "ROUND", level.hud_c_label);
state.round_number = service_text(self, state, 54, 27, "left", 1.9, "", level.hud_c_count);
state.holders_label = service_text(self, state, 10, 52, "left", 1.0, "RECORD HOLDERS", level.hud_c_label);
for (i = 0; i < 3; i++)
state.rows[i] = service_text(self, state, 10, 65 + i * 13,
"left", 1.1, "", level.hud_c_count);
state.empty = service_text(self, state, 10, 40, "left", 1.1,
"No recorded high round yet", level.hud_c_count);
state.unavailable = service_text(self, state, 10, 40, "left", 1.1,
"Map record unavailable", level.hud_c_count);
for (i = 0; i < state.elems.size; i++)
{
state.elems[i].alpha = 0;
// Keep temporary private services out of the archived HUD bucket.
// Hidden elements still occupy slots; do not allocate one per page.
state.elems[i].archived = false;
}
self thread service_loop(state);
self thread service_disconnect(state);
self thread service_end_game(state);
}
// Allocate only the active view. Alpha-zero elements still occupy HUD slots.
service_view_create(state, request)
{
service_view_destroy(state);
state.view_kind = request.kind;
state.view_paged = request.pages > 1;
if (state.view_paged)
state.page_counter = service_text(self, state, level.hud_service_right,
level.hud_service_title_y, "right", 1.0, "", level.hud_c_dim);
if (request.kind == "help")
{
state.help_title = service_text(self, state, level.hud_service_left,
level.hud_service_title_y, "left", 1.1, "COMMANDS", level.hud_c_label);
rows = state.commands.size;
if (rows > level.hud_service_help_rows)
rows = level.hud_service_help_rows;
// Explicit lines also work on T5, which can collapse multiline strings.
for (i = 0; i < rows; i++)
{
y = level.hud_service_help_y + i * level.hud_service_help_pitch;
state.command_rows[i] = service_text(self, state, level.hud_service_left,
y, "left", 1.0, "", level.hud_c_count);
state.description_rows[i] = service_text(self, state, level.hud_service_description_x,
y, "left", 1.0, "", level.hud_c_label);
}
return;
}
state.record_title = service_text(self, state, level.hud_service_right,
level.hud_service_record_y, "right", 1.1, "", level.hud_c_count);
for (i = 0; i < 3; i++)
state.rows[i] = service_text(self, state, level.hud_service_right,
level.hud_service_holders_y + i * level.hud_service_holders_pitch,
"right", 1.05, "", level.hud_c_label);
if (isDefined(state.page_counter))
state.page_counter.y = level.hud_service_holders_y + 3 * level.hud_service_holders_pitch;
}
service_view_destroy(state)
{
for (i = 0; i < state.elems.size; i++)
{
if (isDefined(state.elems[i]))
state.elems[i] Destroy();
}
state.elems = [];
state.view_kind = undefined;
state.view_paged = undefined;
state.page_counter = undefined;
state.help_title = undefined;
state.command_rows = [];
state.description_rows = [];
state.record_title = undefined;
state.rows = [];
state.page_text = undefined;
state.record_header_text = undefined;
state.record_text = [];
state.help_page = -1;
}
service_text(player, state, x, y, align, scale, text, colour)
{
elem = make_panel_text(player, x, level.hud_service_y + y, align, scale, colour);
elem = make_overlay_text(player, x, y, align, scale, colour);
elem.alpha = 0;
elem.archived = false;
if (text != "")
elem SetText(text);
state.elems[state.elems.size] = elem;
@@ -351,74 +409,70 @@ service_render(state)
state.rendered = true;
state.last_generation = generation;
state.last_page = page;
if (!isDefined(request))
{
service_view_destroy(state);
return;
}
paged = request.pages > 1;
if (!isDefined(state.view_kind) || state.view_kind != request.kind || state.view_paged != paged)
service_view_create(state, request);
for (i = 0; i < state.elems.size; i++)
state.elems[i].alpha = 0;
if (!isDefined(request))
return;
state.background.alpha = level.hud_panel_alpha;
state.page_number SetValue(page + 1);
state.page_total SetValue(request.pages);
state.page_number.alpha = 1;
state.page_slash.alpha = 1;
state.page_total.alpha = 1;
if (paged)
{
page_text = "" + (page + 1) + " / " + request.pages;
if (!isDefined(state.page_text) || state.page_text != page_text)
{
state.page_counter SetText(page_text);
state.page_text = page_text;
}
state.page_counter.alpha = 1;
}
if (request.kind == "help")
{
state.help_title.alpha = 1;
state.help_footer.alpha = 1;
if (state.help_page != page)
for (i = 0; i < state.command_rows.size; i++)
{
command_text = "";
description_text = "";
for (i = 0; i < 4; i++)
index = page * level.hud_service_help_rows + i;
if (index >= state.commands.size)
continue;
if (state.help_page != page)
{
index = page * 4 + i;
if (index < state.commands.size)
{
if (i > 0)
{
command_text += "\n";
description_text += "\n";
}
entry = state.commands[index];
command_text += help_command_label(entry.command);
description_text += help_description(entry);
}
entry = state.commands[index];
state.command_rows[i] SetText(help_command_label(entry.command));
state.description_rows[i] SetText(help_description(entry));
}
state.command_column SetText(command_text);
state.description_column SetText(description_text);
state.help_page = page;
state.command_rows[i].alpha = 1;
state.description_rows[i].alpha = 1;
}
state.command_back.alpha = 0.9;
state.command_column.alpha = 1;
state.description_column.alpha = 1;
state.help_page = page;
return;
}
state.record_title.alpha = 1;
state.record_footer.alpha = 1;
header = "Map record unavailable";
snapshot = request.snapshot;
if (!isDefined(snapshot))
if (isDefined(snapshot))
{
state.unavailable.alpha = 1;
return;
header = "No recorded high round yet";
if (snapshot.round > 0)
header = "Map record · Round " + snapshot.round;
}
if (snapshot.round == 0)
if (!isDefined(state.record_header_text) || state.record_header_text != header)
{
state.empty.alpha = 1;
return;
state.record_title SetText(header);
state.record_header_text = header;
}
state.round_label.alpha = 1;
state.round_number SetValue(snapshot.round);
state.round_number.alpha = 1;
state.holders_label.alpha = 1;
state.record_title.alpha = 1;
if (!isDefined(snapshot) || snapshot.round == 0)
return;
for (i = 0; i < 3; i++)
{
index = page * 3 + i;
if (index < snapshot.lines.size)
{
text = snapshot.lines[index];
// Skip identical text when replaying a page or resuming after help.
if (!isDefined(state.record_text[i]) || state.record_text[i] != text)
{
state.rows[i] SetText(text);
@@ -461,12 +515,7 @@ service_destroy(state)
state.closed = true;
state.help = undefined;
state.record = undefined;
for (i = 0; i < state.elems.size; i++)
{
if (isDefined(state.elems[i]))
state.elems[i] Destroy();
}
state.elems = [];
service_view_destroy(state);
}
service_game_over_via(event)
@@ -526,7 +575,8 @@ make_text_elem(player, x, y, align_x, scale, colour)
elem.x = x;
elem.y = y;
elem.foreground = 1;
elem.fontScale = scale;
// Sub-1.0 text can render oversized on clients; keep a safe minimum.
elem.fontScale = max(1.0, scale);
elem.alpha = 1;
elem.color = colour;
@@ -553,14 +603,59 @@ make_shader_elem(player, x, y)
return elem;
}
// Panel x/y are local to the fixed upper-right core origin. A right screen
// anchor keeps the panel on-screen across aspect ratios; negative offsets grow
// left from that edge. Keep each engine's existing vertical anchor convention.
// Absolute fullscreen overlays leave stock HUD areas and the center clear.
make_overlay_text(player, x, y, align_x, scale, colour)
{
elem = make_text_elem(player, x, y, align_x, scale, colour);
elem.horzAlign = "fullscreen";
elem.vertAlign = "fullscreen";
elem.sort = 20;
elem.archived = true;
return elem;
}
make_overlay_shader(player, x, y, width, height, colour, alpha, sort)
{
elem = make_shader_elem(player, x, y);
elem.horzAlign = "fullscreen";
elem.vertAlign = "fullscreen";
elem.color = colour;
elem.alpha = alpha;
elem.sort = sort;
elem.archived = true;
elem SetShader("white", make_overlay_width(width), int(max(1, height)));
return elem;
}
make_overlay_width(width)
{
// Native max() returns a float; shader dimensions must be integers.
return int(max(1, width));
}
make_core_overlay_text(player, x, y, align_x, scale, colour)
{
elem = make_overlay_text(player, x, y, align_x, scale, colour);
register_core_elem(player, elem);
return elem;
}
make_core_overlay_shader(player, x, y, width, height, colour, alpha, sort)
{
elem = make_overlay_shader(player, x, y, width, height, colour, alpha, sort);
register_core_elem(player, elem);
return elem;
}
// Panel x/y are local to the upper-right core origin. Fullscreen bypasses the
// engine safe area. Horizontal conversion preserves the original size at 16:9;
// other aspects retain the right-edge anchor with proportional horizontal size.
make_panel_text(player, x, y, align_x, scale, colour)
{
elem = make_text_elem(player, x - level.hud_panel_width - level.hud_panel_right,
elem = make_text_elem(player, 640 + (x - level.hud_panel_width - level.hud_panel_right) * level.hud_panel_x_scale,
level.hud_panel_top + y, align_x, scale, colour);
elem.horzAlign = "right";
elem.horzAlign = "fullscreen";
elem.vertAlign = "fullscreen";
elem.sort = 20;
elem.archived = true;
return elem;
@@ -568,17 +663,87 @@ make_panel_text(player, x, y, align_x, scale, colour)
make_panel_shader(player, x, y, width, height, colour, alpha, sort)
{
elem = make_shader_elem(player, x - level.hud_panel_width - level.hud_panel_right,
elem = make_shader_elem(player, 640 + (x - level.hud_panel_width - level.hud_panel_right) * level.hud_panel_x_scale,
level.hud_panel_top + y);
elem.horzAlign = "right";
elem.horzAlign = "fullscreen";
elem.vertAlign = "fullscreen";
elem.color = colour;
elem.alpha = alpha;
elem.sort = sort;
elem.archived = true;
elem SetShader("white", width, height);
elem SetShader("white", make_panel_width(width), height);
return elem;
}
// Native shader widths are integers; dynamic fills use the same conversion.
make_panel_width(width)
{
// max() returns a float even for integer inputs; cast its final result.
return int(max(1, width * level.hud_panel_x_scale));
}
// Core elements share a visibility mask; services keep their independent alpha.
make_core_text(player, x, y, align_x, scale, colour)
{
elem = make_panel_text(player, x, y, align_x, scale, colour);
register_core_elem(player, elem);
return elem;
}
make_core_shader(player, x, y, width, height, colour, alpha, sort)
{
elem = make_panel_shader(player, x, y, width, height, colour, alpha, sort);
register_core_elem(player, elem);
return elem;
}
register_core_elem(player, elem)
{
live = [];
if (isDefined(player.tws_core_hud_elems))
{
for (i = 0; i < player.tws_core_hud_elems.size; i++)
{
old = player.tws_core_hud_elems[i];
if (isDefined(old))
live[live.size] = old;
}
}
live[live.size] = elem;
player.tws_core_hud_elems = live;
set_core_alpha(player, elem, elem.alpha);
}
set_core_alpha(player, elem, alpha)
{
if (!isDefined(elem))
return;
elem.tws_core_alpha = alpha;
elem.alpha = alpha;
if (isDefined(player.tws_hud_hidden) && player.tws_hud_hidden)
elem.alpha = 0;
}
toggle_core_hud()
{
hidden = false;
if (isDefined(self.tws_hud_hidden))
hidden = self.tws_hud_hidden;
self.tws_hud_hidden = !hidden;
if (!isDefined(self.tws_core_hud_elems))
return;
live = [];
for (i = 0; i < self.tws_core_hud_elems.size; i++)
{
elem = self.tws_core_hud_elems[i];
if (!isDefined(elem))
continue;
set_core_alpha(self, elem, elem.tws_core_alpha);
live[live.size] = elem;
}
self.tws_core_hud_elems = live;
}
destroy_on_disconnect(elems)
{
self waittill("disconnect");
+72 -47
View File
@@ -1,4 +1,4 @@
/* tws_t5_hud.gsc - fixed upper-right core HUD, visible from connection. */
/* tws_t5_hud.gsc - minimal edge HUD, toggleable with .hud. */
#include maps\_utility;
#include common_scripts\utility;
@@ -9,14 +9,34 @@ init()
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()
@@ -27,48 +47,42 @@ hud_connect()
return;
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.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 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");
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.
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, "--");
// 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_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);
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_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_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);
@@ -76,7 +90,7 @@ hud_connect()
hud_text(x, y, align, scale, colour, text)
{
elem = scripts\sp\tws_t5_common::make_panel_text(self, x, y, align, scale, colour);
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;
@@ -145,7 +159,7 @@ hud_timer_row()
{
self endon("disconnect");
// A visible panel for a waiting newcomer must not start a pre-set round 1.
// 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)
@@ -190,7 +204,7 @@ match_timer_bind(elem)
elem SetTimerUp(0);
}
// The panel remains visible at game over, with authoritative frozen clocks.
// Preserve authoritative frozen clocks when the core is shown after game over.
freeze_timers_on_game_over(match_elem, round_elem)
{
self endon("disconnect");
@@ -249,11 +263,12 @@ hud_health_display()
self endon("disconnect");
level endon("hud_game_over");
bar_width = level.hud_bar_width;
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;
@@ -272,16 +287,17 @@ hud_health_display()
if (frac > 1)
frac = 1;
width = int(max(frac * bar_width, 1));
width = scripts\sp\tws_t5_common::make_overlay_width(frac * bar_width);
if (width != last_width)
{
bar SetShader("white", width, level.hud_bar_height);
bar SetShader("white", width, level.hud_overlay_bar_height);
last_width = width;
}
bar.alpha = 1;
bar_alpha = 1;
if (frac == 0)
bar.alpha = 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)
@@ -293,8 +309,17 @@ hud_health_display()
cur.color = colour;
slash.color = colour;
max_hp.color = colour;
hp.color = colour;
// Health numbers remain visible at full health.
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);