469 lines
23 KiB
Markdown
469 lines
23 KiB
Markdown
# TWS T5 Zombies scripts
|
|
|
|
Custom HUD + AFK toggle + immediate join + lobby lock + game telemetry + map records for Plutonium T5
|
|
(Black Ops 1) zombies dedicated servers. Split across seven `.gsc` files,
|
|
with `tws_t5_common` as the shared hub. Lobby lock also requires the HUD module.
|
|
|
|
Platform quirks and deploy commands: [NOTES.md](NOTES.md).
|
|
|
|
## Files
|
|
|
|
| File | Role |
|
|
|---|---|
|
|
| [tws_t5_common.gsc](tws_t5_common.gsc) | Shared config, HUD helpers, `.help`, connect/spawn registries, single `connected` listener + dispatcher |
|
|
| [tws_t5_hud.gsc](tws_t5_hud.gsc) | Bottom-left HUD stack: timers, zombie count, health bar |
|
|
| [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 |
|
|
| [tws_t5_telemetry.gsc](tws_t5_telemetry.gsc) | Direct HTTP game start, round, heartbeat, and game end reports |
|
|
| [tws_t5_leaderboard.gsc](tws_t5_leaderboard.gsc) | Private map high-round record and all tied holders on connection |
|
|
|
|
## Install / upgrade
|
|
|
|
Copy `tws_t5_*.gsc` to `storage/t5/scripts/sp/`, then restart the map.
|
|
When upgrading an existing installation, first remove these old scripts from
|
|
that directory:
|
|
|
|
- `tws_bank.gsc` and `tws_db.gsc` (removed modules)
|
|
- `tws_common.gsc`, `tws_hud.gsc`, `tws_afk.gsc`, `tws_join.gsc`,
|
|
`tws_lock.gsc`, `tws_telemetry.gsc` and `tws_leaderboard.gsc` (renamed modules)
|
|
|
|
Do not leave both common scripts installed: duplicate connection listeners
|
|
break T5's connect handshake. Existing dvar names are unchanged.
|
|
|
|
## Map record announcements
|
|
|
|
Copy `tws_t5_leaderboard.gsc` and the updated `tws_t5_common.gsc` to
|
|
`storage/t5/scripts/sp/`, then restart the map. Leaderboard requires a
|
|
[t5-gsc-utils](https://github.com/alicealys/t5-gsc-utils) build exposing
|
|
`http::request`, `jsonParse`, `typeof` and `formatString`. A plugin upgrade
|
|
requires a full server-process restart. To omit leaderboard, remove its script;
|
|
the enable dvar does not bypass native linking dependencies.
|
|
|
|
Records are read from the public Electric Cherry API with no credentials and
|
|
work independently of telemetry and HUD. Spectator newcomers receive
|
|
the same private announcement as playable joins. T5 matches its engine map to
|
|
the API's map list. [Shared configuration, caching and validation](../README.md#map-record-announcements)
|
|
apply to both games.
|
|
|
|
## Player commands
|
|
|
|
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 appears below the health bar for 12 seconds, clear of the timers, zombie
|
|
counter and health display. Typing either command again restarts that display time.
|
|
|
|
| Command | Effect |
|
|
|---|---|
|
|
| `.help`, `.commands` | 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.
|
|
|
|
## Dependency graph
|
|
|
|
Arrows read "depends on" — solid means the source names the target with
|
|
`scripts\sp\tws_t5_common::...`, dashed means the source waits on a level
|
|
notify fired by the target.
|
|
|
|
```mermaid
|
|
graph BT
|
|
common[tws_t5_common<br/><i>config + helpers + dispatcher</i>]
|
|
|
|
hud[tws_t5_hud]
|
|
afk[tws_t5_afk]
|
|
join[tws_t5_join]
|
|
lock[tws_t5_lock]
|
|
telemetry[tws_t5_telemetry]
|
|
leaderboard[tws_t5_leaderboard]
|
|
|
|
hud --> common
|
|
afk --> common
|
|
join --> common
|
|
lock --> common
|
|
telemetry --> common
|
|
leaderboard --> common
|
|
|
|
lock -. hud_round_changed<br/>hud_game_over .-> hud
|
|
```
|
|
|
|
- `tws_t5_common` is the only module named by other files at compile time.
|
|
- All other cross-module coupling flows through level notifies
|
|
(`hud_round_changed`, `hud_match_started`,
|
|
`hud_game_over`) or through spawn handlers registered with
|
|
`tws_t5_common::register_spawn_handler` / `register_connect_handler`.
|
|
- Any file except `tws_t5_common` can be removed; the rest still load.
|
|
`tws_t5_afk` has no downstream consumers — pull it independently.
|
|
`tws_t5_lock` needs `tws_t5_hud` to receive round and game-over notifications.
|
|
|
|
## Runtime notifies
|
|
|
|
| Notify | Fired by | Read by |
|
|
|---|---|---|
|
|
| `hud_round_changed` | `tws_t5_hud` | `tws_t5_hud` (round-timer reset), `tws_t5_lock` |
|
|
| `hud_match_started` | `tws_t5_hud` | `tws_t5_hud` (match-timer reveal) |
|
|
| `hud_game_over` | `tws_t5_hud` | `tws_t5_hud` (freeze timers), `tws_t5_lock` (clear password) |
|
|
|
|
---
|
|
|
|
## tws_t5_common.gsc
|
|
|
|
Hub file. Shared config, HUD helpers, spawn-handler registry, and the
|
|
single `level waittill("connected")` in the fleet. Adding a second
|
|
connect listener anywhere else crashes the T5 SP handshake.
|
|
|
|
**Features**
|
|
- Shared HUD constants: `level.hud_x`, `hud_y`, `hud_row_*`, `hud_col_*`,
|
|
`hud_font_*`, `hud_bar_*`, `hud_c_*`, `hud_rate`, `hud_health_threshold`
|
|
- Spawn-handler registry
|
|
- Connection-handler registry for optional modules that serve spectator newcomers
|
|
- `.help` listener and help registry populated by installed command modules
|
|
- Connect listener + per-player dispatch: iterates
|
|
`level.tws_spawn_handlers` after `spawned_player` and threads each on
|
|
the player
|
|
|
|
**Provides**
|
|
| Function | Purpose |
|
|
|---|---|
|
|
| `common_config()` | Populate every `level.hud_*` (idempotent) |
|
|
| `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_connect_handler(fn)` | Threads `::fn` after connection dispatch yields, without waiting for playable spawn |
|
|
| `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 |
|
|
| `destroy_on_disconnect(elems)` | Waits on `disconnect`, destroys the array |
|
|
| `int_dvar_or(name, fallback)` | Parse an integer dvar with default |
|
|
| `format_mmss(elapsed_ms)` | Format milliseconds as `M:SS` |
|
|
|
|
**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_t5_hud.gsc
|
|
|
|
Bottom-left cluster. Per-player elements spawn via `::hud_start`.
|
|
|
|
**Features**
|
|
- Match timer + round timer (round resets each round, both freeze on game over)
|
|
- Player-name row (only visible in co-op)
|
|
- Zombies-remaining counter
|
|
- Health bar with green / amber / red ramp; numbers fade in below full HP
|
|
- Round-transition and game-over notify machinery
|
|
|
|
**Provides**
|
|
| Notify / level var | Meaning |
|
|
|---|---|
|
|
| notify `hud_round_changed` | Fires each time `level.round_number` transitions |
|
|
| notify `hud_match_started` | Fires once when the match clock seeds |
|
|
| notify `hud_game_over` | Fires on the first `end_game` / `game_ended` / `intermission` notify |
|
|
| `level.round_start_time` | Anchored at first observed round transition |
|
|
| `level.match_start_time` | Anchored at first observed round-1 (or on first player spawn) |
|
|
|
|
Spawn handler registered: `::hud_start` — threads `hud_timer_row`,
|
|
`hud_zombie_counter`, `hud_health_display`.
|
|
|
|
**Depends on**
|
|
- `scripts\sp\tws_t5_common::` — `ensure_config`, `make_text_elem`,
|
|
`make_shader_elem`, `destroy_on_disconnect`, `format_mmss`,
|
|
`register_spawn_handler`
|
|
- Every `level.hud_*` config value from `tws_t5_common`
|
|
- Stock BO1 state: `level.round_number`, `level.zombie_total`,
|
|
`level.players`, `self.health`, `self.maxhealth`, `self.name` /
|
|
`self.playername`, AI entities' `.animname` and `.ignore_enemy_count`
|
|
|
|
---
|
|
|
|
## tws_t5_afk.gsc
|
|
|
|
Self-invoked pause mode. Owns the `[AFK]` indicator element entirely — HUD
|
|
module has zero knowledge of AFK.
|
|
|
|
**Features**
|
|
- Chat commands `.afk` and `.back` (via `level waittill("say")` — not
|
|
`callbackPlayerSay`, which never fires on T5 SP dedicated)
|
|
- On enter: `enableInvulnerability`, `freezeControls(true)`,
|
|
`self.ignoreme = true` (drops out of zombie targeting)
|
|
- 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
|
|
|
|
**Provides**
|
|
- Per-player: `self.hud_afk_on` (bool), `self.hud_afk_elem` (HUD element),
|
|
writes `self.ignoreme` (stock BO1 targeting flag)
|
|
- Level var: `level.afk_grace_seconds` (default 2)
|
|
|
|
**Depends on**
|
|
- `scripts\sp\tws_t5_common::` — `ensure_config`, `make_text_elem`,
|
|
`destroy_on_disconnect`, `register_spawn_handler`, `register_help_command`
|
|
- Shared config: `level.hud_x`, `level.hud_row_match`,
|
|
`level.hud_font_small`, `level.hud_c_afk`
|
|
- Stock BO1: `enableInvulnerability`, `disableInvulnerability`,
|
|
`freezeControls`, `.ignoreme`, `level waittill("say", message, player)`
|
|
|
|
Spawn handler registered: `::afk_spawn`.
|
|
|
|
---
|
|
|
|
## tws_t5_join.gsc
|
|
|
|
Type `.join` in chat while spectating after joining a match in progress to
|
|
spawn immediately. Install with `tws_t5_common.gsc`; no HUD dependency.
|
|
|
|
**Eligibility**
|
|
|
|
- Only before the player's first playable spawn on the current connection.
|
|
- A normal spawn or successful `.join` permanently closes eligibility for
|
|
that connection. Getting downed, bleeding out, or changing rounds never
|
|
restores it. Dead players must wait for the normal round-end respawn.
|
|
- Requests during connection setup, without a living teammate, or after
|
|
game over cannot spawn a player. Repeated `.join` requests cannot create
|
|
overlapping spawns.
|
|
- This is connection-local tracking; reconnecting creates a new player and
|
|
resets eligibility. It is not an account-level reconnect restriction.
|
|
|
|
Uses the stock `level.spawnPlayer` entry point and preserves map-specific
|
|
`level.custom_spawnPlayer` callbacks. If absent, installs the stock zombies
|
|
spectator callback, as the round-end routine does. Missing spectator spawn
|
|
locations are seeded from the map's `initial_spawn_points`.
|
|
|
|
Spawn handler registered: `::join_spawn`. Stock `self.initialized` provides
|
|
an additional first-spawn guard; `hasSpawned` cannot distinguish spectators.
|
|
See the [stock zombies lifecycle](https://github.com/plutoniummod/t5-scripts/blob/main/ZM/Common/maps/_zombiemode.gsc)
|
|
and [spawn entry point](https://github.com/plutoniummod/t5-scripts/blob/main/ZM/Common/maps/_callbackglobal.gsc).
|
|
|
|
**Live validation (requires a T5 server)**
|
|
|
|
1. Join during an active round, type `.join`, and verify one playable spawn
|
|
with normal weapons, controls, zombie targeting, and HUD initialization.
|
|
Check both round 1 and a later round, including each deployed map.
|
|
2. Repeat `.join` rapidly; verify no duplicate spawns. Try it alive, while
|
|
downed, and after bleeding out; verify no revive or respawn.
|
|
3. Join and wait for the normal round-end spawn instead; after dying, verify
|
|
`.join` remains blocked, including on subsequent rounds.
|
|
4. Try during round transition and game over; verify no duplicate spawn or
|
|
restart of an ended match. Check that `.afk` / `.back` still work.
|
|
|
|
---
|
|
|
|
## tws_t5_lock.gsc
|
|
|
|
**Features**
|
|
- Sets `g_password` to a random per-lobby value when the round crosses a
|
|
milestone (default: round 20)
|
|
- Existing players are unaffected; `g_password` is only checked at connect
|
|
- Clears `g_password` on `hud_game_over` so the next lobby opens unlocked
|
|
- Trigger round is dvar-driven — retune without redeploying
|
|
|
|
**Provides**
|
|
| Level var | Meaning |
|
|
|---|---|
|
|
| `level.lock_round` | Round at which the lobby locks (read at init) |
|
|
| `level.lock_active` | `true` after the password has been set |
|
|
|
|
**Depends on**
|
|
- `scripts\sp\tws_t5_common::` — `ensure_config`, `int_dvar_or`
|
|
- `tws_t5_hud` — waits on `hud_round_changed` and `hud_game_over`
|
|
- Stock BO1: `level.round_number`
|
|
- Dvar: `tws_lock_round` (int, default `20`)
|
|
|
|
---
|
|
|
|
## Dvars
|
|
|
|
| Dvar | Consumer | Default | Purpose |
|
|
|---|---|---|---|
|
|
| `zombiemode` | every module | — | Gate: every `init()` returns early unless `"1"` |
|
|
| `tws_lock_round` | `tws_t5_lock` | `20` | Round at which the lobby locks |
|
|
| `tws_server_id` | `tws_t5_telemetry` | Required for telemetry | Exact registered Electric Cherry server name |
|
|
| `tws_telemetry_enabled` | `tws_t5_telemetry` | `1` | Set to `0` to disable telemetry at init |
|
|
| `tws_telemetry_url` | `tws_t5_telemetry` | `https://ec.thewrightserver.net/api/telemetry` | Electric Cherry telemetry endpoint for JSON POSTs |
|
|
| `tws_telemetry_log_level` | `tws_t5_telemetry` | `1` | Read live: `0` warnings/errors/recovery only, `1` lifecycle/request/health logs, `2` also capture/queue logs and response previews |
|
|
|
|
## tws_t5_telemetry.gsc
|
|
|
|
Uses `t5-gsc-utils` HTTP/JSON components to POST directly to Electric Cherry's
|
|
`/api/telemetry`. Install alongside `tws_t5_common.gsc` in `storage/t5/scripts/sp/`.
|
|
No HUD dependency. Reports run whether or not a beta is active.
|
|
Follow the shared [authentication setup](../README.md#telemetry-authentication-t5-and-t6)
|
|
to configure the service before upgrading. The reporter token is embedded in
|
|
`level.telemetry_token`; it must match the service's `EC_TELEMETRY_TOKEN`. Short
|
|
embedded tokens and non-HTTPS URLs disable reporting at init.
|
|
|
|
Example server config (except log level, read at script init; restart the map after changes):
|
|
|
|
```cfg
|
|
set tws_server_id "t5_zombie_prototype_1"
|
|
set tws_telemetry_enabled "1"
|
|
set tws_telemetry_url "https://ec.thewrightserver.net/api/telemetry"
|
|
set tws_telemetry_log_level "1"
|
|
```
|
|
|
|
`tws_server_id` must match the registered server name exactly. If it is
|
|
unset, the script logs a configuration error and does not start reporting.
|
|
An unknown or destroyed server is rejected by Electric Cherry with HTTP 404.
|
|
|
|
The reporter emits exactly four event types:
|
|
|
|
| Event | When | Payload |
|
|
|---|---|---|
|
|
| `game_start` | First playable spawn, with the stock `begin_spawning` flag as a fallback | `server`, `map`, `players` with `name` and `guid` |
|
|
| `round_change` | First observed round after start, then each change (checked every 0.5 seconds) | `server`, `map`, `round`, full player stats |
|
|
| `heartbeat` | Every 30 seconds after `game_start`, until game end | Same fields as `round_change`, with current round and player stats |
|
|
| `game_end` | First stock `end_game`, `game_ended`, or `intermission` notification | `server`, `map`, final `round`, full player stats |
|
|
|
|
Each player in round/heartbeat/end reports has `name`, `guid`, `kills`, `headshots`,
|
|
`downs`, `revives`, and `score`. Names and GUIDs remain strings; GUIDs never
|
|
pass through numeric conversion. Round and stats are nonnegative integers,
|
|
with missing or negative stat values reported as zero. The four counters
|
|
come from T5's `player.stats`; `score` is the player's current spendable
|
|
`player.score`, rather than cumulative points earned. `map` is `mapname`.
|
|
|
|
Every report includes everyone returned by `getPlayers()`, including
|
|
spectators, downed players, and AFK players. Empty rosters are sent as `[]`.
|
|
Undefined players and players with missing/empty names or GUIDs are skipped
|
|
with a warning containing their roster index; a later snapshot can include
|
|
them once their identity is available.
|
|
The script builds the JSON array delimiters explicitly because the plugin
|
|
serializes an empty GSC array as `null`; player objects and strings still
|
|
use `jsonSerialize` for escaping.
|
|
|
|
```json
|
|
{"event":"game_start","server":"t5_zombie_prototype_1","map":"zombie_cod5_prototype","players":[{"name":"Josh","guid":"12345678901234567"}]}
|
|
{"event":"round_change","server":"t5_zombie_prototype_1","map":"zombie_cod5_prototype","round":17,"players":[{"name":"Josh","guid":"12345678901234567","kills":420,"headshots":160,"downs":1,"revives":3,"score":32000}]}
|
|
{"event":"heartbeat","server":"t5_zombie_prototype_1","map":"zombie_cod5_prototype","round":17,"players":[{"name":"Josh","guid":"12345678901234567","kills":430,"headshots":165,"downs":1,"revives":3,"score":32500}]}
|
|
{"event":"game_end","server":"t5_zombie_prototype_1","map":"zombie_cod5_prototype","round":18,"players":[{"name":"Josh","guid":"12345678901234567","kills":445,"headshots":170,"downs":2,"revives":3,"score":33500}]}
|
|
```
|
|
|
|
Events are captured immediately into an in-memory queue. One HTTP worker
|
|
POSTs each snapshot once, in gameplay order, with `Content-Type:
|
|
application/json` and `Authorization: Bearer <token>`, waiting for completion
|
|
before sending the next. Gameplay and round detection continue while requests
|
|
are pending. Repeated joins or
|
|
end notifications do not duplicate start/end reports. At game over, the
|
|
reporter captures any round change since the last poll, stops detection,
|
|
and lets the sender drain through `game_end`.
|
|
|
|
The first heartbeat is captured 30 seconds after match start, then every
|
|
30 seconds even when the round is unchanged or the roster is empty. Round
|
|
changes and later spawns do not reset the interval or add heartbeat workers.
|
|
Heartbeats use the same snapshot builder and ordered sender as round reports;
|
|
pending HTTP requests can delay transmission. Game end stops the heartbeat
|
|
worker, and already queued heartbeats drain before `game_end`.
|
|
|
|
There is no discovery, beta token, session ID, sequence field, custom event,
|
|
external helper, or separate beta acknowledgement. Delivery is best effort:
|
|
no retries or persistence across map reloads, and no cross-game exactly-once
|
|
guarantee. A pending request delays later reports; the current plugin has no
|
|
request timeout option. A map reload can discard queued reports.
|
|
|
|
Transport failures log to the server console. The plugin does not expose
|
|
HTTP status codes, so its success flag does not establish API acceptance.
|
|
Verify acceptance at Electric Cherry: success is HTTP 200 with `ok: true`
|
|
and telemetry containing `receivedAt`; invalid payloads return 400 and
|
|
non-JSON content types return 415.
|
|
|
|
**Diagnosing a lobby that stops reporting**
|
|
|
|
Default logging includes initialization, match start/end, each request's
|
|
event/round and local ID, queue wait and request duration in milliseconds,
|
|
transport failures/recovery, and the final queue drain. IDs reset on map
|
|
load and only appear in logs; payloads are unchanged. Every log includes
|
|
the server, map, and game time in milliseconds.
|
|
|
|
An independent watchdog prints health every 30 seconds, even before match
|
|
start and while HTTP is pending or the game-end queue is draining. It shows
|
|
the sender stage, current snapshot capture, pending queue size (excluding
|
|
the in-flight request), total queued, completed transports, failures, and
|
|
time since the last completion (`-1` means none). Warnings remain enabled
|
|
at log level `0`:
|
|
|
|
- `HTTP stalled`: a request has been pending for at least 30 seconds. The
|
|
warning repeats on watchdog ticks with its ID, event, age, and queue size.
|
|
- `sender is not consuming`: the oldest queued report has waited at least
|
|
30 seconds with no in-flight request.
|
|
- `round watcher has stopped advancing`: no polling progress for over
|
|
5 seconds, detected on the next watchdog tick.
|
|
- `no snapshot captured`: an active match has gone at least 65 seconds
|
|
without a new snapshot, despite its 30-second heartbeat.
|
|
|
|
Set `tws_telemetry_log_level "2"` in the server console while investigating;
|
|
no map restart is needed. This adds capture/queue stages and API response
|
|
previews, escaped onto one line and limited to the first 256 characters.
|
|
Look for `ok: false` and the rejection reason in those previews; HTML may
|
|
indicate a proxy error. Previews can include player identities echoed by
|
|
the API. Restore `1` afterward, or use `0` to retain only warnings, errors,
|
|
and transport recovery messages.
|
|
|
|
Missing request handles and reported transport failures are counted and
|
|
skipped so the sender can continue with the next event. Empty responses
|
|
produce a warning. Arbitrary response bodies are not parsed as JSON,
|
|
because malformed/proxy responses must not break the sender. The watchdog
|
|
does not cancel or retry stuck requests: the current plugin offers neither
|
|
timeout nor cancellation through GSC, and starting another request could
|
|
reorder events or accumulate native workers. A stall remains blocked until
|
|
the plugin completes it; these logs identify that condition, not fix it.
|
|
|
|
Requires a plugin build containing
|
|
[`http::request`](https://github.com/alicealys/t5-gsc-utils/blob/main/src/component/http.cpp)
|
|
and [`jsonSerialize`](https://github.com/alicealys/t5-gsc-utils/blob/main/src/component/json.cpp).
|
|
Disabling the dvar does not remove that compile-time dependency; remove the
|
|
script when using a plugin build without those functions.
|
|
|
|
Stat and lifecycle references: [stock T5 zombies](https://github.com/plutoniummod/t5-scripts/blob/main/ZM/Common/maps/_zombiemode.gsc)
|
|
and [score tracking](https://github.com/plutoniummod/t5-scripts/blob/main/ZM/Common/maps/_zombiemode_score.gsc).
|
|
|
|
**Live validation (requires a T5 server)**
|
|
|
|
1. Start a match and verify one `game_start`, then an initial `round_change`
|
|
and one per new round. Check the exact server name, map, string GUIDs,
|
|
all player identities, and all five stats against the game.
|
|
2. Join again in the same round, go AFK, become downed, and spectate. Verify
|
|
the next report includes the full roster without another start event.
|
|
3. End a match and verify one `game_end` with final stats after all round
|
|
reports, including if more than one stock end notification fires. Check
|
|
a round-0 intro and an empty roster, including last-player disconnect.
|
|
4. Delay HTTP completion while rounds advance and the match ends. Verify
|
|
requests remain ordered and retain the stats captured at each event.
|
|
With the API unreachable, verify transport-error logging and continued
|
|
gameplay. Repeat without an active beta and with the HUD module removed.
|
|
5. Stay in one round for over a minute. Verify heartbeats at 30 and 60 seconds
|
|
after start, with current stats and the same fields as `round_change`.
|
|
Check that round changes and joins do not reset or duplicate the interval,
|
|
and no heartbeat follows `game_end`, including with requests pending.
|
|
6. Change log level between `0`, `1`, and `2` without a map restart. Check
|
|
request IDs pair sends with completions and level `2` shows bounded,
|
|
escaped response previews. Use an unregistered server name on a test
|
|
server and confirm the API rejection is visible in the response even
|
|
though transport completes. Also check an empty or HTML response.
|
|
7. Point a test server at an endpoint that accepts a connection but does
|
|
not reply. Verify recurring `HTTP stalled` warnings and increasing
|
|
pending counts while gameplay continues, including after game over.
|
|
Release the delayed reply and verify the queue resumes in order and
|
|
the watchdog stops after the final drain. Test a refused connection
|
|
separately to check failure counts and the recovery log once restored.
|
|
|
|
## External dependencies
|
|
|
|
[t5-gsc-utils](https://github.com/alicealys/t5-gsc-utils) is required for telemetry
|
|
and leaderboard HTTP, JSON, type and string helpers. Install a build exposing
|
|
the natives listed in each module's documentation, then restart the server
|
|
process. No database is required.
|