Add modular chat help for T5 and T6

This commit is contained in:
2026-10-04 14:51:58 -04:00
parent 6b462c4726
commit bbafebe208
10 changed files with 248 additions and 10 deletions
+1 -1
View File
@@ -76,7 +76,7 @@ Copy all `tws_*.gsc` to `storage/t5/scripts/sp/` on the Plutonium T5 server
| File | Role | Fires notifies | Reads notifies | | File | Role | Fires notifies | Reads notifies |
|---|---|---|---| |---|---|---|---|
| `tws_common` | Shared config, HUD helpers, spawn-handler registry, single connect listener + dispatcher | — | — | | `tws_common` | Shared config, HUD helpers, `.help` registry, spawn-handler registry, single connect listener + dispatcher | — | `say` |
| `tws_db` | MariaDB pool config + reachability probe | `db_probe_done` | — | | `tws_db` | MariaDB pool config + reachability probe | `db_probe_done` | — |
| `tws_hud` | Timer row, zombie count, health bar | `hud_round_changed`, `hud_match_started`, `hud_game_over` | (internal) | | `tws_hud` | Timer row, zombie count, health bar | `hud_round_changed`, `hud_match_started`, `hud_game_over` | (internal) |
| `tws_bank` | Persistent bank + `bank` / `deposit` / `withdraw` commands | — | `db_probe_done` | | `tws_bank` | Persistent bank + `bank` / `deposit` / `withdraw` commands | — | `db_probe_done` |
+35 -2
View File
@@ -11,7 +11,7 @@ Platform quirks and deploy commands: [NOTES.md](NOTES.md).
| File | Role | | File | Role |
|---|---| |---|---|
| [tws_common.gsc](tws_common.gsc) | Shared config, HUD helpers, spawn-handler registry, single `connected` listener + dispatcher | | [tws_common.gsc](tws_common.gsc) | Shared config, HUD helpers, `.help`, spawn-handler registry, single `connected` listener + dispatcher |
| [tws_db.gsc](tws_db.gsc) | MariaDB pool config + reachability probe | | [tws_db.gsc](tws_db.gsc) | MariaDB pool config + reachability probe |
| [tws_hud.gsc](tws_hud.gsc) | Bottom-left HUD stack: timers, zombie count, health bar | | [tws_hud.gsc](tws_hud.gsc) | Bottom-left HUD stack: timers, zombie count, health bar |
| [tws_bank.gsc](tws_bank.gsc) | Persistent per-player bank + console commands | | [tws_bank.gsc](tws_bank.gsc) | Persistent per-player bank + console commands |
@@ -20,6 +20,25 @@ Platform quirks and deploy commands: [NOTES.md](NOTES.md).
| [tws_lock.gsc](tws_lock.gsc) | Lobby `g_password` lock at a milestone round | | [tws_lock.gsc](tws_lock.gsc) | Lobby `g_password` lock at a milestone round |
| [tws_telemetry.gsc](tws_telemetry.gsc) | Direct HTTP game start, round, heartbeat, and game end reports | | [tws_telemetry.gsc](tws_telemetry.gsc) | Direct HTTP game start, round, heartbeat, and game end reports |
## Player commands
Type `.help` 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 appears below the health bar for 12 seconds, clear of the timers, zombie
counter and health display. Typing `.help` again restarts that display time.
| Command | Effect |
|---|---|
| `.help` | List available commands |
| `.afk` | Pause with protection |
| `.back` | Return from AFK |
| `.join` | Join mid-round (newcomers only) |
Each module registers its own help entries; removing AFK or join also removes
those entries from `.help` after a map restart. The bank's `bank`, `deposit`, and
`withdraw` commands are server-console commands and are not listed in chat help.
## Dependency graph ## Dependency graph
Arrows read "depends on" — solid means the source names the target with Arrows read "depends on" — solid means the source names the target with
@@ -80,6 +99,7 @@ connect listener anywhere else crashes the T5 SP handshake.
- Shared HUD constants: `level.hud_x`, `hud_y`, `hud_row_*`, `hud_col_*`, - Shared HUD constants: `level.hud_x`, `hud_y`, `hud_row_*`, `hud_col_*`,
`hud_font_*`, `hud_bar_*`, `hud_c_*`, `hud_rate`, `hud_health_threshold` `hud_font_*`, `hud_bar_*`, `hud_c_*`, `hud_rate`, `hud_health_threshold`
- Spawn-handler registry - Spawn-handler registry
- `.help` listener and help registry populated by installed command modules
- Connect listener + per-player dispatch: iterates - Connect listener + per-player dispatch: iterates
`level.tws_spawn_handlers` after `spawned_player` and threads each on `level.tws_spawn_handlers` after `spawned_player` and threads each on
the player the player
@@ -90,6 +110,7 @@ connect listener anywhere else crashes the T5 SP handshake.
| `common_config()` | Populate every `level.hud_*` (idempotent) | | `common_config()` | Populate every `level.hud_*` (idempotent) |
| `ensure_config()` | No-op if config already set; called by every module's `init` | | `ensure_config()` | No-op if config already set; called by every module's `init` |
| `register_spawn_handler(fn)` | Appends `::fn` to `level.tws_spawn_handlers` | | `register_spawn_handler(fn)` | Appends `::fn` to `level.tws_spawn_handlers` |
| `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_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_shader_elem(p, x, y)` | `NewClientHudElem` shader element |
| `destroy_on_disconnect(elems)` | Waits on `disconnect`, destroys the array | | `destroy_on_disconnect(elems)` | Waits on `disconnect`, destroys the array |
@@ -98,6 +119,18 @@ connect listener anywhere else crashes the T5 SP handshake.
**Depends on**: nothing (leaf module). **Depends on**: nothing (leaf module).
**Live validation (requires a T5 server)**
1. Type `.help` and `.HELP` while playing and while spectating before the first
spawn. Verify all four descriptions appear below the health bar, only for the
requesting player. Take damage so health numbers are visible and check that
they do not cover the help text.
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. Repeat `.help` before 12 seconds elapse; verify one list stays visible for
12 seconds after the latest request, then hides. Reopen it after expiry,
die/respawn and reconnect; verify no duplicate text or leftover help elements.
--- ---
## tws_db.gsc ## tws_db.gsc
@@ -219,7 +252,7 @@ module has zero knowledge of AFK.
**Depends on** **Depends on**
- `scripts\sp\tws_common::` — `ensure_config`, `make_text_elem`, - `scripts\sp\tws_common::` — `ensure_config`, `make_text_elem`,
`destroy_on_disconnect`, `register_spawn_handler` `destroy_on_disconnect`, `register_spawn_handler`, `register_help_command`
- Shared config: `level.hud_x`, `level.hud_row_match`, - Shared config: `level.hud_x`, `level.hud_row_match`,
`level.hud_font_small`, `level.hud_c_afk` `level.hud_font_small`, `level.hud_c_afk`
- Stock BO1: `enableInvulnerability`, `disableInvulnerability`, - Stock BO1: `enableInvulnerability`, `disableInvulnerability`,
+2
View File
@@ -13,6 +13,8 @@ init()
return; return;
scripts\sp\tws_common::ensure_config(); scripts\sp\tws_common::ensure_config();
scripts\sp\tws_common::register_help_command(".afk", "Pause with protection");
scripts\sp\tws_common::register_help_command(".back", "Return from AFK");
afk_init(); afk_init();
+81 -1
View File
@@ -1,4 +1,4 @@
/* tws_common.gsc — shared config, HUD helpers, spawn-handler registry, connect dispatcher. */ /* tws_common.gsc — shared config, HUD helpers, spawn/help registries, connect dispatcher. */
#include maps\_utility; #include maps\_utility;
#include common_scripts\utility; #include common_scripts\utility;
@@ -10,6 +10,8 @@ init()
common_config(); common_config();
register_help_command(".help", "List available commands");
level thread help_chat_listener();
level thread on_player_connect_dispatch(); level thread on_player_connect_dispatch();
} }
@@ -72,6 +74,84 @@ register_spawn_handler(fn)
level.tws_spawn_handlers[level.tws_spawn_handlers.size] = fn; level.tws_spawn_handlers[level.tws_spawn_handlers.size] = fn;
} }
// Modules register their own entries so help only lists installed commands.
register_help_command(command, description)
{
if (!isDefined(level.tws_help_commands))
level.tws_help_commands = [];
entry = spawnStruct();
entry.command = command;
entry.description = description;
level.tws_help_commands[level.tws_help_commands.size] = entry;
}
// Listen at level scope so newcomers can ask for help before their first spawn.
help_chat_listener()
{
for (;;)
{
level waittill("say", message, player);
if (!isDefined(player) || !isDefined(message))
continue;
if (toLower(message) == ".help")
player show_help();
}
}
show_help()
{
// Cache the text per connection; repeated SetText calls consume config strings.
if (!isDefined(self.tws_help_elems))
{
elems = [];
// Use the same anchors as the HUD, with room below the health numbers.
y = level.hud_row_bar + level.hud_bar_height + 20;
title = make_text_elem(self, level.hud_x, y,
"left", level.hud_font_mid, level.hud_c_afk);
title SetText("Commands");
elems[elems.size] = title;
for (i = 0; i < level.tws_help_commands.size; i++)
{
entry = level.tws_help_commands[i];
elem = make_text_elem(self, level.hud_x, y + 22 + i * 18,
"left", level.hud_font_small, level.hud_c_count);
elem SetText("^3" + entry.command + "^7 - " + entry.description);
elems[elems.size] = elem;
}
self.tws_help_elems = elems;
self thread destroy_on_disconnect(elems);
}
for (i = 0; i < self.tws_help_elems.size; i++)
{
elem = self.tws_help_elems[i];
elem.alpha = 1;
}
// Only the expiry timer is replaced; the say listener and cleanup stay alive.
self notify("tws_help_refresh");
self thread hide_help_after_delay();
}
hide_help_after_delay()
{
self endon("disconnect");
self endon("tws_help_refresh");
wait 12;
for (i = 0; i < self.tws_help_elems.size; i++)
{
elem = self.tws_help_elems[i];
elem.alpha = 0;
}
}
// only connect listener in the fleet; a second one crashes the T5 SP handshake // only connect listener in the fleet; a second one crashes the T5 SP handshake
on_player_connect_dispatch() on_player_connect_dispatch()
{ {
+1
View File
@@ -9,6 +9,7 @@ init()
return; return;
scripts\sp\tws_common::ensure_config(); scripts\sp\tws_common::ensure_config();
scripts\sp\tws_common::register_help_command(".join", "Join mid-round (newcomers only)");
scripts\sp\tws_common::register_spawn_handler(::join_spawn); scripts\sp\tws_common::register_spawn_handler(::join_spawn);
level thread join_chat_listener(); level thread join_chat_listener();
+23 -3
View File
@@ -23,6 +23,16 @@ HUD again owns the `hud_game_over` notification fan-out. Lock again waits on HUD
notifications, applies T5's random password, and clears it on startup/game over. notifications, applies T5's random password, and clears it on startup/game over.
The added password-preservation dvars and independent lock watcher are gone. The added password-preservation dvars and independent lock watcher are gone.
Common also listens for `.help` through the native `say` notification, independent
of first-spawn initialization so newcomers can view commands while spectating.
Each loaded command module registers its command and description with
`register_help_command()` during `init()`. Lazy registry initialization preserves
registrations regardless of module initialization order. Help uses client HUD
elements below the health bar, sharing the HUD's anchors to avoid overlap with
timers, zombie count and health numbers. Text is created once per connection and
reused; each request restarts a 12-second visibility timer. Disconnect cleanup
is independent of that timer. The command handlers retain their existing listeners.
AFK again listens to the native `say` notification, so it no longer needs plugin AFK again listens to the native `say` notification, so it no longer needs plugin
chat registration. The additional T6 controls are only held while AFK; `.back` chat registration. The additional T6 controls are only held while AFK; `.back`
releases them before the protection grace period. Restoration is deferred during releases them before the protection grace period. Restoration is deferred during
@@ -82,9 +92,10 @@ at link time. The other four TWS modules can run without telemetry.
## Checks ## Checks
Local: all five files pass gsc-tool 1.4.10 T6 PC compilation; shared references and Local: `tools/check.ps1` compiles all six files for T6 PC and checks shared
entry points are checked by `tools/check.ps1`. No live server was run for this references and entry points. It requires PowerShell and gsc-tool. The `.help`
baseline simplification. Compiler success does not certify runtime linking. change passed the static entry-point/reference checks, but compilation and live
gameplay were not run. Compiler success does not certify runtime linking.
Live checks after deploying: Live checks after deploying:
@@ -97,3 +108,12 @@ Live checks after deploying:
4. Telemetry sends the initial occupied round and each new round to the existing 4. Telemetry sends the initial occupied round and each new round to the existing
endpoint. Check server ID/player count, same-round joins and an unreachable API. endpoint. Check server ID/player count, same-round joins and an unreachable API.
5. Repeat on the actual server maps, especially afterlife and special-enemy rounds. 5. Repeat on the actual server maps, especially afterlife and special-enemy rounds.
6. Type `.help` and `.HELP` before first spawn, during play and while AFK: see
`.help`, `.afk`, `.back` and `.join` with descriptions, only on the requesting
player's screen below the health bar. Take damage so health numbers appear
and verify they do not overlap help. Repeat the command before 12 seconds
elapse: one list should remain visible until 12 seconds after the latest
request. Reopen after expiry and reconnect; verify no duplicates or leftovers.
Remove the AFK or Join module, restart the map and confirm its commands are
absent. Restore the modules and confirm `.afk`, `.back` and `.join` still
behave as before.
+22 -2
View File
@@ -5,7 +5,7 @@ just like T5. There is no separate TWS config file or database setup.
| File | Role | Dependencies | | File | Role | Dependencies |
| --- | --- | --- | | --- | --- | --- |
| `tws_t6_common.gsc` | Shared HUD values, helpers and first-spawn dispatcher | Stock T6 | | `tws_t6_common.gsc` | Shared HUD values, helpers, `.help` and first-spawn dispatcher | Stock T6 |
| `tws_t6_hud.gsc` | Match/round timers, name, zombie count and health bar | Common | | `tws_t6_hud.gsc` | Match/round timers, name, zombie count and health bar | Common |
| `tws_t6_afk.gsc` | `.afk` / `.back`, protection, movement and weapon blocking | Common | | `tws_t6_afk.gsc` | `.afk` / `.back`, protection, movement and weapon blocking | Common |
| `tws_t6_lock.gsc` | Lock at round 20; clear password on game over | Common, HUD | | `tws_t6_lock.gsc` | Lock at round 20; clear password on game over | Common, HUD |
@@ -31,6 +31,26 @@ Keep the filenames and `scripts/zm` location so shared references resolve.
To omit telemetry, remove its script; disabling it at runtime does not remove To omit telemetry, remove its script; disabling it at runtime does not remove
its plugin dependency during linking. its plugin dependency during linking.
## Chat commands
Type `.help` in chat to see the commands supplied by the loaded TWS modules and
a short description of each. The list is shown only to the requesting player,
including spectators who have not spawned. Commands are case-insensitive.
Help appears below the health bar for 12 seconds, clear of the timers, zombie
counter and health display. Typing `.help` again restarts that display time.
| Command | Description |
| --- | --- |
| `.help` | List available commands |
| `.afk` | Pause with protection |
| `.back` | Return from AFK |
| `.join` | Join mid-round (newcomers only) |
AFK and Join register their commands during `init()` through
`tws_t6_common::register_help_command(command, description)`. If a module is
omitted, its commands are omitted from `.help`. Register new commands alongside
their module's initialization so the list stays current.
## Defaults, matching T5 ## Defaults, matching T5
- HUD values live in `tws_t6_common::common_config()`. That function and - HUD values live in `tws_t6_common::common_config()`. That function and
@@ -95,7 +115,7 @@ another mod installed one first.
## Validation ## Validation
All six scripts compile for T6 PC with gsc-tool 1.4.10. To repeat: To compile all six scripts for T6 PC with gsc-tool 1.4.10:
```powershell ```powershell
.\tools\check.ps1 -CompilerPath C:\path\to\gsc-tool.exe .\tools\check.ps1 -CompilerPath C:\path\to\gsc-tool.exe
+2
View File
@@ -10,6 +10,8 @@ main()
init() init()
{ {
scripts\zm\tws_t6_common::ensure_config(); scripts\zm\tws_t6_common::ensure_config();
scripts\zm\tws_t6_common::register_help_command(".afk", "Pause with protection");
scripts\zm\tws_t6_common::register_help_command(".back", "Return from AFK");
afk_init(); afk_init();
scripts\zm\tws_t6_common::register_spawn_handler(::afk_spawn); scripts\zm\tws_t6_common::register_spawn_handler(::afk_spawn);
} }
+80 -1
View File
@@ -1,4 +1,4 @@
/* tws_t6_common.gsc — shared config, HUD helpers, spawn-handler registry, connect dispatcher. */ /* tws_t6_common.gsc — shared config, HUD helpers, help and spawn-handler registries. */
#include maps\mp\_utility; #include maps\mp\_utility;
#include common_scripts\utility; #include common_scripts\utility;
@@ -11,7 +11,9 @@ main()
init() init()
{ {
common_config(); common_config();
register_help_command(".help", "List available commands");
level thread help_chat_listener();
level thread on_player_connect_dispatch(); level thread on_player_connect_dispatch();
} }
@@ -74,6 +76,83 @@ register_spawn_handler(fn)
level.tws_spawn_handlers[level.tws_spawn_handlers.size] = fn; level.tws_spawn_handlers[level.tws_spawn_handlers.size] = fn;
} }
// Modules register their own commands, regardless of init order.
register_help_command(command, description)
{
if (!isDefined(level.tws_help_commands))
level.tws_help_commands = [];
entry = spawnStruct();
entry.command = command;
entry.description = description;
level.tws_help_commands[level.tws_help_commands.size] = entry;
}
help_chat_listener()
{
for (;;)
{
level waittill("say", message, player);
if (!isDefined(player) || !isDefined(message))
continue;
if (toLower(message) == ".help")
player show_help();
}
}
show_help()
{
// Cache the text per connection; repeated SetText calls consume config strings.
if (!isDefined(self.tws_help_elems))
{
elems = [];
// Use the same anchors as the HUD, with room below the health numbers.
y = level.hud_row_bar + level.hud_bar_height + 20;
title = make_text_elem(self, level.hud_x, y,
"left", level.hud_font_mid, level.hud_c_afk);
title SetText("Commands");
elems[elems.size] = title;
for (i = 0; i < level.tws_help_commands.size; i++)
{
entry = level.tws_help_commands[i];
elem = make_text_elem(self, level.hud_x, y + 22 + i * 18,
"left", level.hud_font_small, level.hud_c_count);
elem SetText("^3" + entry.command + "^7 - " + entry.description);
elems[elems.size] = elem;
}
self.tws_help_elems = elems;
self thread destroy_on_disconnect(elems);
}
for (i = 0; i < self.tws_help_elems.size; i++)
{
elem = self.tws_help_elems[i];
elem.alpha = 1;
}
// Only the expiry timer is replaced; the say listener and cleanup stay alive.
self notify("tws_help_refresh");
self thread hide_help_after_delay();
}
hide_help_after_delay()
{
self endon("disconnect");
self endon("tws_help_refresh");
wait 12;
for (i = 0; i < self.tws_help_elems.size; i++)
{
elem = self.tws_help_elems[i];
elem.alpha = 0;
}
}
// Shared dispatcher: one HUD instance and account load per connection. // Shared dispatcher: one HUD instance and account load per connection.
on_player_connect_dispatch() on_player_connect_dispatch()
{ {
+1
View File
@@ -14,6 +14,7 @@ init()
println("[tws_t6_join] init reached"); println("[tws_t6_join] init reached");
scripts\zm\tws_t6_common::ensure_config(); scripts\zm\tws_t6_common::ensure_config();
scripts\zm\tws_t6_common::register_help_command(".join", "Join mid-round (newcomers only)");
scripts\zm\tws_t6_common::register_spawn_handler(::join_spawn); scripts\zm\tws_t6_common::register_spawn_handler(::join_spawn);
level thread join_chat_listener(); level thread join_chat_listener();