Remove T5 bank and database modules and standardize script names

This commit is contained in:
2026-10-06 00:09:46 -04:00
parent 5521f4ea43
commit c5d937dc64
14 changed files with 153 additions and 516 deletions
+9 -13
View File
@@ -5,7 +5,7 @@ Combined working repo for the TWS Plutonium Zombies GSC scripts across two games
## Layout
- [t5/](t5/) — Plutonium **T5** (Call of Duty: Black Ops 1) Zombies scripts. Most-developed baseline. See [t5/README.md](t5/README.md) and [t5/NOTES.md](t5/NOTES.md).
- [t6/](t6/) — Plutonium **T6** (Call of Duty: Black Ops 2) Zombies scripts. Ported from T5; at parity on common, HUD, AFK, lock, telemetry, join and leaderboard. Only `db` and `bank` remain T5-only. See [t6/README.md](t6/README.md), [t6/NOTES.md](t6/NOTES.md), and [t6/tools/check.ps1](t6/tools/check.ps1).
- [t6/](t6/) — Plutonium **T6** (Call of Duty: Black Ops 2) Zombies scripts. Ported from T5; at parity on common, HUD, AFK, lock, telemetry, join and leaderboard. See [t6/README.md](t6/README.md), [t6/NOTES.md](t6/NOTES.md), and [t6/tools/check.ps1](t6/tools/check.ps1).
## Parity
@@ -15,21 +15,17 @@ Modules currently in each tree:
| Module | T5 | T6 |
|------------|-------------------------------|---------------------------------|
| common | `t5/tws_common.gsc` | `t6/tws_t6_common.gsc` |
| hud | `t5/tws_hud.gsc` | `t6/tws_t6_hud.gsc` |
| afk | `t5/tws_afk.gsc` | `t6/tws_t6_afk.gsc` |
| lock | `t5/tws_lock.gsc` | `t6/tws_t6_lock.gsc` |
| telemetry | `t5/tws_telemetry.gsc` | `t6/tws_t6_telemetry.gsc` — queued `game_start` / `heartbeat` / `round_change` / `game_end` matching T5 |
| join | `t5/tws_join.gsc` | `t6/tws_t6_join.gsc` |
| leaderboard | `t5/tws_leaderboard.gsc` | `t6/tws_t6_leaderboard.gsc` |
| db | `t5/tws_db.gsc` | *(not ported)* |
| bank | `t5/tws_bank.gsc` | *(not ported)* |
`db` and `bank` are T5-only for now.
| common | `t5/tws_t5_common.gsc` | `t6/tws_t6_common.gsc` |
| hud | `t5/tws_t5_hud.gsc` | `t6/tws_t6_hud.gsc` |
| afk | `t5/tws_t5_afk.gsc` | `t6/tws_t6_afk.gsc` |
| lock | `t5/tws_t5_lock.gsc` | `t6/tws_t6_lock.gsc` |
| telemetry | `t5/tws_t5_telemetry.gsc` | `t6/tws_t6_telemetry.gsc` — queued `game_start` / `heartbeat` / `round_change` / `game_end` matching T5 |
| join | `t5/tws_t5_join.gsc` | `t6/tws_t6_join.gsc` |
| leaderboard | `t5/tws_t5_leaderboard.gsc` | `t6/tws_t6_leaderboard.gsc` |
## Map record announcements
Install `tws_leaderboard.gsc` for T5 or `tws_t6_leaderboard.gsc` for T6,
Install `tws_t5_leaderboard.gsc` for T5 or `tws_t6_leaderboard.gsc` for T6,
**along with the updated common module** for that game.
The new module privately announces the current map's fleet-wide highest recorded
round and every tied holder once per connection, including spectator newcomers.
+18 -36
View File
@@ -1,16 +1,19 @@
# tws-t5-aio notes
# TWS T5 script notes
Reference for the `tws_*.gsc` files in this repo. Platform quirks and
Reference for the `tws_t5_*.gsc` files in this repo. Platform quirks and
load-bearing decisions that show up as invariants in the code.
## Install
Copy all `tws_*.gsc` to `storage/t5/scripts/sp/` on the Plutonium T5 server
Copy all `tws_t5_*.gsc` to `storage/t5/scripts/sp/` on the Plutonium T5 server
(v2 Plutainer: `app/runtime/plutonium/storage/t5/scripts/sp/`).
- `sudo chown -R 1000:1000 <app>` — Plutainer runs as UID 1000
- `docker exec <container> rcon-cli map_restart` — reloads scripts in place
For existing installations, follow the [upgrade cleanup](README.md#install--upgrade)
before copying the renamed scripts.
## Platform quirks (T5 SP engine, Plutonium)
- **Initialize locals before loops when reading them afterwards.** T5 rejected
@@ -18,9 +21,9 @@ Copy all `tws_*.gsc` to `storage/t5/scripts/sp/` on the Plutonium T5 server
inside the map-search loop, even though its later use was guarded by the
match count. It now starts as `undefined` before that loop in both versions.
The JavaScript fixture harness and T6 compiler did not catch this T5 error.
- **One `level waittill("connected")` only.** `tws_common` owns it. A
- **One `level waittill("connected")` only.** `tws_t5_common` owns it. A
second listener breaks the T5 SP connect handshake. Per-player work goes
through `tws_common::register_spawn_handler` after `spawned_player`, or
through `tws_t5_common::register_spawn_handler` after `spawned_player`, or
`register_connect_handler` for spectator-safe work after the dispatcher yields.
Leaderboard uses the latter; its level worker owns all HTTP work.
- **Chat uses `level waittill("say", ...)`**, not `callbackPlayerSay`. T5 SP
@@ -39,9 +42,9 @@ Copy all `tws_*.gsc` to `storage/t5/scripts/sp/` on the Plutonium T5 server
- **Zombies namespace is WaW-derived** (`maps\_zombiemode_*`), not BO2's
`maps\mp\zombies\_zm_*`.
- **Check `t5-gsc-utils` source as well as its README.** Current source
includes `http::request` and `jsonSerialize` / `jsonParse`, in addition
to file IO, console commands and MySQL. Telemetry requires a DLL built
with these HTTP/JSON components; older builds may lack them.
includes `http::request` and `jsonSerialize` / `jsonParse`. Telemetry and
leaderboard require a DLL built with these HTTP/JSON components; older
builds may lack them.
See [HTTP source](https://github.com/alicealys/t5-gsc-utils/blob/main/src/component/http.cpp)
and [JSON source](https://github.com/alicealys/t5-gsc-utils/blob/main/src/component/json.cpp).
@@ -59,35 +62,14 @@ Copy all `tws_*.gsc` to `storage/t5/scripts/sp/` on the Plutonium T5 server
is intercepted by the zombiemode scripts before it reaches our scope, and
polling `self.health` misses the zero-crossing (health is rewritten during
the game-over sequence).
- **Bank writes DB-first, memory-second.** Every mutation writes to the DB
and only updates `self.bank` / `self.score` after the write confirms. A
dead DB can never create phantom money. Deposits use `INSERT ... ON
DUPLICATE KEY UPDATE`; withdrawals gate on `WHERE balance >= ?` so the DB
itself blocks overdrafts under concurrent commands.
- **Bank command surface is `command::add_sv` (server console), not chat.**
SP-engine chat is unreliable and `chat::register_command` isn't available
in `t5-gsc-utils`.
## Current state / caveats
- `bank_load` in `tws_bank.gsc` is a **diagnostic stub**: bare minimum to
isolate what specifically kills the connect handshake. Do not restore the
stripped `getGuid` / MySQL body until the stub passes join cleanly.
- DB credentials in `tws_db.gsc` are hardcoded for the initial fleet test.
Move to dvars (`tws_db_host` / `tws_db_user` / `tws_db_password` /
`tws_db_name`) before this repo goes public — otherwise the fleet's DB
password lives in source.
## Module map
| File | Role | Fires notifies | Reads notifies |
|---|---|---|---|
| `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_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_afk` | `.afk` / `.back` chat toggle, owns its [AFK] indicator | — | — |
| `tws_join` | `.join` for spectators who have not played on this connection | — | `say`, `end_game`, `game_ended`, `intermission` |
| `tws_lock` | Lobby `g_password` lock at milestone round (`tws_lock_round`, default 20) | — | `hud_round_changed`, `hud_game_over` |
| `tws_telemetry` | Ordered JSON game start, round, 30-second heartbeat, and game end reports with full player snapshots | `telemetry_queued`, `telemetry_finished` (internal) | `end_game`, `game_ended`, `intermission` |
| `tws_leaderboard` | Cached completed-match map record and all tied holders, privately on join | — | `end_game`, `game_ended`, `intermission` |
| `tws_t5_common` | Shared config, HUD helpers, `.help` registry, spawn-handler registry, single connect listener + dispatcher | — | `say` |
| `tws_t5_hud` | Timer row, zombie count, health bar | `hud_round_changed`, `hud_match_started`, `hud_game_over` | (internal) |
| `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` |
| `tws_t5_telemetry` | Ordered JSON game start, round, 30-second heartbeat, and game end reports with full player snapshots | `telemetry_queued`, `telemetry_finished` (internal) | `end_game`, `game_ended`, `intermission` |
| `tws_t5_leaderboard` | Cached completed-match map record and all tied holders, privately on join | — | `end_game`, `game_ended`, `intermission` |
+70 -134
View File
@@ -1,9 +1,8 @@
# tws-t5-aio
# TWS T5 Zombies scripts
Custom HUD + persistent bank + AFK toggle + immediate join + lobby lock + game telemetry + map records for Plutonium T5
(Black Ops 1) zombies dedicated servers. Split across nine `.gsc` files,
each installable / removable in isolation (with `tws_common` as the shared
hub).
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).
@@ -11,19 +10,30 @@ Platform quirks and deploy commands: [NOTES.md](NOTES.md).
| File | Role |
|---|---|
| [tws_common.gsc](tws_common.gsc) | Shared config, HUD helpers, `.help`, connect/spawn registries, single `connected` listener + dispatcher |
| [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_bank.gsc](tws_bank.gsc) | Persistent per-player bank + console commands |
| [tws_afk.gsc](tws_afk.gsc) | `.afk` / `.back` chat toggle + `[AFK]` indicator |
| [tws_join.gsc](tws_join.gsc) | `.join` lets newcomers spawn before the current round ends |
| [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_leaderboard.gsc](tws_leaderboard.gsc) | Private map high-round record and all tied holders on connection |
| [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_leaderboard.gsc` and the updated `tws_common.gsc` to
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
@@ -31,7 +41,7 @@ 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, HUD and the bank. Spectator newcomers receive
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.
@@ -52,62 +62,55 @@ counter and health display. Typing `.help` again restarts that display time.
| `.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.
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_common::...`, dashed means the source waits on a level
notify fired by the target. Transitive edges are elided (so `tws_bank`
and `tws_lock` reach `tws_common` through `tws_db` / `tws_hud` on the
graph, but they also call `tws_common` directly).
`scripts\sp\tws_t5_common::...`, dashed means the source waits on a level
notify fired by the target.
```mermaid
graph BT
common[tws_common<br/><i>config + helpers + dispatcher</i>]
common[tws_t5_common<br/><i>config + helpers + dispatcher</i>]
db[tws_db]
hud[tws_hud]
afk[tws_afk]
join[tws_join]
bank[tws_bank]
lock[tws_lock]
telemetry[tws_telemetry]
leaderboard[tws_leaderboard]
hud[tws_t5_hud]
afk[tws_t5_afk]
join[tws_t5_join]
lock[tws_t5_lock]
telemetry[tws_t5_telemetry]
leaderboard[tws_t5_leaderboard]
db --> common
hud --> common
afk --> common
join --> common
lock --> common
telemetry --> common
leaderboard --> common
bank -. db_probe_done .-> db
lock -. hud_round_changed<br/>hud_game_over .-> hud
```
- `tws_common` is the only module named by other files at compile time.
- `tws_t5_common` is the only module named by other files at compile time.
- All other cross-module coupling flows through level notifies
(`db_probe_done`, `hud_round_changed`, `hud_match_started`,
(`hud_round_changed`, `hud_match_started`,
`hud_game_over`) or through spawn handlers registered with
`tws_common::register_spawn_handler` / `register_connect_handler`.
- Any file except `tws_common` can be removed; the rest still load.
`tws_afk` has no downstream consumers — pull it independently.
`tws_bank` needs `tws_db`; `tws_lock` needs `tws_hud`.
`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 |
|---|---|---|
| `db_probe_done` | `tws_db` | `tws_bank` (via `bank_deferred_start`) |
| `hud_round_changed` | `tws_hud` | `tws_hud` (round-timer reset), `tws_lock` |
| `hud_match_started` | `tws_hud` | `tws_hud` (match-timer reveal) |
| `hud_game_over` | `tws_hud` | `tws_hud` (freeze timers), `tws_lock` (clear password) |
| `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_common.gsc
## 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
@@ -153,31 +156,7 @@ connect listener anywhere else crashes the T5 SP handshake.
---
## tws_db.gsc
**Features**
- One MariaDB connection pool for the whole script (used by bank now,
leaderboard later)
- Reachability probe: round-trips `SELECT 1` and flips `level.db_online`
- Fires `db_probe_done` so waiters can proceed regardless of outcome
**Provides**
| Level var / notify | Meaning |
|---|---|
| `level.db_online` | `true` iff the probe query returned without error |
| `level.db_probe_done` | `true` once the probe has completed (either way) |
| notify `db_probe_done` | Fired once after probe completion |
**Depends on**
- `scripts\sp\tws_common::ensure_config()`
- `t5-gsc-utils` (`mysql::set_config`, `mysql::query`)
**Config (currently hardcoded — TODO: dvarize before repo goes public)**
- Host `mariadb`, port `3306`, DB `tws_zm`, user `josh`, password `joshster`
---
## tws_hud.gsc
## tws_t5_hud.gsc
Bottom-left cluster. Per-player elements spawn via `::hud_start`.
@@ -201,56 +180,17 @@ Spawn handler registered: `::hud_start` — threads `hud_timer_row`,
`hud_zombie_counter`, `hud_health_display`.
**Depends on**
- `scripts\sp\tws_common::` — `ensure_config`, `make_text_elem`,
- `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_common`
- 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_bank.gsc
Persistent per-player bank. Balances survive server hops within the fleet
because rows are keyed on XUID.
**Features**
- Server-console commands: `bank`, `deposit <amount>`, `withdraw <amount>`
- DB-first / memory-second: `self.bank` and `self.score` only move after
the DB write confirms
- Deposits: `INSERT ... ON DUPLICATE KEY UPDATE` — lazy row create, atomic
- Withdrawals: `WHERE balance >= ?` — DB blocks overdrafts under concurrency
- Score-drift refund guard on deposit
- `bank_load` currently a **diagnostic stub** (bare-minimum to isolate the
connect-handshake crash — do not restore the stripped body until it passes
join)
**Provides**
| Command | Args | Effect |
|---|---|---|
| `bank` | — | Prints current `Bank` and `Score` to the caller |
| `deposit` | `<amount>` | Score → bank, DB-first with refund on race |
| `withdraw` | `<amount>` | Bank → score, DB-first with overdraft guard |
Per-player state: `self.bank` (int), `self.bank_unavailable` (bool),
`self.bank_guid` (set in the full impl — currently unset by the stub).
**Depends on**
- `scripts\sp\tws_common::` — `ensure_config`, `register_spawn_handler`
- `tws_db` — waits on `db_probe_done`, gates on `level.db_online`
- `t5-gsc-utils` — `mysql::query`, `mysql::prepared_statement`,
`command::add_sv`
- Stock BO1: `self.score`
- Dvar: `tws_server_id` (optional; labels `last_server` on bank rows)
- External: MariaDB schema — `bank(guid, balance, last_name, last_server)`
Spawn handler registered: `::bank_load`.
---
## tws_afk.gsc
## tws_t5_afk.gsc
Self-invoked pause mode. Owns the `[AFK]` indicator element entirely — HUD
module has zero knowledge of AFK.
@@ -271,7 +211,7 @@ module has zero knowledge of AFK.
- Level var: `level.afk_grace_seconds` (default 2)
**Depends on**
- `scripts\sp\tws_common::` — `ensure_config`, `make_text_elem`,
- `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`
@@ -282,10 +222,10 @@ Spawn handler registered: `::afk_spawn`.
---
## tws_join.gsc
## tws_t5_join.gsc
Type `.join` in chat while spectating after joining a match in progress to
spawn immediately. Install with `tws_common.gsc`; no HUD or DB dependency.
spawn immediately. Install with `tws_t5_common.gsc`; no HUD dependency.
**Eligibility**
@@ -323,7 +263,7 @@ and [spawn entry point](https://github.com/plutoniummod/t5-scripts/blob/main/ZM/
---
## tws_lock.gsc
## tws_t5_lock.gsc
**Features**
- Sets `g_password` to a random per-lobby value when the round crosses a
@@ -339,8 +279,8 @@ and [spawn entry point](https://github.com/plutoniummod/t5-scripts/blob/main/ZM/
| `level.lock_active` | `true` after the password has been set |
**Depends on**
- `scripts\sp\tws_common::` — `ensure_config`, `int_dvar_or`
- `tws_hud` — waits on `hud_round_changed` and `hud_game_over`
- `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`)
@@ -351,17 +291,17 @@ and [spawn entry point](https://github.com/plutoniummod/t5-scripts/blob/main/ZM/
| Dvar | Consumer | Default | Purpose |
|---|---|---|---|
| `zombiemode` | every module | — | Gate: every `init()` returns early unless `"1"` |
| `tws_lock_round` | `tws_lock` | `20` | Round at which the lobby locks |
| `tws_server_id` | `tws_bank`, `tws_telemetry` | Required for telemetry | Exact registered Electric Cherry server name; also used as the bank label |
| `tws_telemetry_enabled` | `tws_telemetry` | `1` | Set to `0` to disable telemetry at init |
| `tws_telemetry_url` | `tws_telemetry` | `https://ec.thewrightserver.net/api/telemetry` | Electric Cherry telemetry endpoint for JSON POSTs |
| `tws_telemetry_log_level` | `tws_telemetry` | `1` | Read live: `0` warnings/errors/recovery only, `1` lifecycle/request/health logs, `2` also capture/queue logs and response previews |
| `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_telemetry.gsc
## tws_t5_telemetry.gsc
Uses `t5-gsc-utils` HTTP/JSON components to POST directly to Electric Cherry's
`/api/telemetry`. Install alongside `tws_common.gsc` in `storage/t5/scripts/sp/`.
No HUD or database dependency. Reports run whether or not a beta is active.
`/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
@@ -503,7 +443,7 @@ and [score tracking](https://github.com/plutoniummod/t5-scripts/blob/main/ZM/Com
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 HUD/DB modules removed.
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,
@@ -522,11 +462,7 @@ and [score tracking](https://github.com/plutoniummod/t5-scripts/blob/main/ZM/Com
## External dependencies
- **[t5-gsc-utils](https://github.com/alicealys/t5-gsc-utils)** — required.
`tws_db` and `tws_bank` need the `mysql::` namespace; `tws_bank` also
needs `command::add_sv`.
- **MariaDB / MySQL server** reachable from the Plutonium container as
`mariadb:3306` (currently hardcoded — see `tws_db.gsc`).
- **Schema** — `bank(guid VARCHAR PRIMARY KEY, balance INT, last_name
VARCHAR, last_server VARCHAR)`. `tws_bank` lazily inserts rows on first
deposit.
[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.
-229
View File
@@ -1,229 +0,0 @@
/*
tws_bank.gsc — persistent per-player bank, MariaDB-backed. See NOTES.md
for the DB-first / spawn-dispatch / command surface invariants.
*/
#include maps\_utility;
#include common_scripts\utility;
init()
{
if (GetDvar(#"zombiemode") != "1")
return;
scripts\sp\tws_common::ensure_config();
bank_init();
scripts\sp\tws_common::register_spawn_handler(::bank_load);
}
bank_init()
{
if (!isDefined(level.db_probe_done) || !level.db_probe_done)
level thread bank_deferred_start();
else
bank_start();
}
bank_deferred_start()
{
level waittill("db_probe_done");
bank_start();
}
bank_start()
{
if (!level.db_online)
return;
bank_register_commands();
}
bank_register_commands()
{
command::add_sv("bank", ::bank_cmd_bank);
command::add_sv("deposit", ::bank_cmd_deposit);
command::add_sv("withdraw", ::bank_cmd_withdraw);
}
// DIAGNOSTIC STUB — bare minimum to isolate the connect-handshake crash.
// Do not restore the getGuid / mysql body until this passes join.
bank_load()
{
self endon("disconnect");
self iPrintLnBold("^2bank_load: entered");
self.bank = 0;
self.bank_unavailable = false;
self iPrintLnBold("^2bank_load: done");
}
bank_available(player)
{
if (!level.db_online)
{
player iPrintLnBold("^1Bank offline");
return 0;
}
if (is_true(player.bank_unavailable))
{
player iPrintLnBold("^1Bank unavailable for your account");
return 0;
}
if (!isDefined(player.bank))
{
player iPrintLnBold("^3Bank still loading, try again");
return 0;
}
return 1;
}
bank_cmd_bank(args)
{
if (!bank_available(self))
return;
self iPrintLnBold("Bank: " + self.bank + " Score: " + self.score);
}
bank_cmd_deposit(args)
{
if (!bank_available(self))
return;
if (args.size < 2)
{
self iPrintLnBold("Usage: deposit <amount>");
return;
}
amount = int(args[1]);
if (amount <= 0)
{
self iPrintLnBold("^1Amount must be positive");
return;
}
if (amount > self.score)
{
self iPrintLnBold("^1Not enough score");
return;
}
self thread bank_deposit_write(amount);
}
bank_cmd_withdraw(args)
{
if (!bank_available(self))
return;
if (args.size < 2)
{
self iPrintLnBold("Usage: withdraw <amount>");
return;
}
amount = int(args[1]);
if (amount <= 0)
{
self iPrintLnBold("^1Amount must be positive");
return;
}
if (amount > self.bank)
{
self iPrintLnBold("^1Insufficient balance");
return;
}
self thread bank_withdraw_write(amount);
}
// upsert: first deposit creates the row, later ones increment
bank_deposit_write(amount)
{
self endon("disconnect");
name = bank_display_name(self);
server_id = bank_server_id();
q = mysql::prepared_statement(
"INSERT INTO bank (guid, balance, last_name, last_server) VALUES (?, ?, ?, ?) " +
"ON DUPLICATE KEY UPDATE balance = balance + VALUES(balance), " +
"last_name = VALUES(last_name), last_server = VALUES(last_server)",
self.bank_guid, amount, name, server_id);
q waittill("done", result, affected_rows, error);
if (isDefined(error) && error != "")
{
self iPrintLnBold("^1Deposit failed");
return;
}
// score can change between dispatch and write completing — refund if so
if (amount > self.score)
{
mysql::prepared_statement(
"UPDATE bank SET balance = balance - ? WHERE guid = ?",
amount, self.bank_guid);
self iPrintLnBold("^1Deposit rolled back (score changed)");
return;
}
self.score -= amount;
self.bank += amount;
self iPrintLnBold("Deposited " + amount + " Bank: " + self.bank);
}
// balance guard lives in the WHERE so concurrent commands can't overdraw
bank_withdraw_write(amount)
{
self endon("disconnect");
q = mysql::prepared_statement(
"UPDATE bank SET balance = balance - ? WHERE guid = ? AND balance >= ?",
amount, self.bank_guid, amount);
q waittill("done", result, affected_rows, error);
if (isDefined(error) && error != "")
{
self iPrintLnBold("^1Withdraw failed");
return;
}
if (!isDefined(affected_rows) || affected_rows == 0)
{
self iPrintLnBold("^1Insufficient balance");
self thread bank_load(); // re-sync in case memory drifted from DB
return;
}
self.score += amount;
self.bank -= amount;
self iPrintLnBold("Withdrew " + amount + " Bank: " + self.bank);
}
bank_display_name(player)
{
if (isDefined(player.name))
return player.name;
if (isDefined(player.playername))
return player.playername;
return "unknown";
}
bank_server_id()
{
id = GetDvar("tws_server_id");
if (id == "")
return "unknown";
return id;
}
-47
View File
@@ -1,47 +0,0 @@
/* tws_db.gsc — MariaDB pool + reachability probe. Fires db_probe_done. */
#include maps\_utility;
#include common_scripts\utility;
init()
{
if (GetDvar(#"zombiemode") != "1")
return;
scripts\sp\tws_common::ensure_config();
db_init();
}
// TODO: move creds to dvars before this repo goes public
db_init()
{
level.db_online = false;
level.db_probe_done = false;
cfg = spawnStruct();
cfg.host = "mariadb";
cfg.user = "josh";
cfg.password = "joshster";
cfg.port = 3306;
cfg.database = "tws_zm";
mysql::set_config(cfg);
level thread db_probe();
}
// set_config is silent on failure; only a real query surfaces the error
db_probe()
{
q = mysql::query("SELECT 1 AS ok");
q waittill("done", result, affected_rows, error);
if (isDefined(error) && error != "")
level.db_online = false;
else
level.db_online = true;
level.db_probe_done = true;
level notify("db_probe_done");
}
+7 -7
View File
@@ -1,5 +1,5 @@
/*
tws_afk.gsc — .afk / .back chat toggle. Owns the [AFK] indicator element.
tws_t5_afk.gsc — .afk / .back chat toggle. Owns the [AFK] indicator element.
Chat uses level waittill("say") — callbackPlayerSay never fires on T5 SP
dedicated (see NOTES.md).
*/
@@ -12,13 +12,13 @@ init()
if (GetDvar(#"zombiemode") != "1")
return;
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");
scripts\sp\tws_t5_common::ensure_config();
scripts\sp\tws_t5_common::register_help_command(".afk", "Pause with protection");
scripts\sp\tws_t5_common::register_help_command(".back", "Return from AFK");
afk_init();
scripts\sp\tws_common::register_spawn_handler(::afk_spawn);
scripts\sp\tws_t5_common::register_spawn_handler(::afk_spawn);
}
afk_init()
@@ -50,7 +50,7 @@ afk_spawn()
{
self endon("disconnect");
afk = scripts\sp\tws_common::make_text_elem(
afk = scripts\sp\tws_t5_common::make_text_elem(
self, level.hud_x + 42, level.hud_row_match,
"left", level.hud_font_small, level.hud_c_afk);
afk SetText("[AFK]");
@@ -59,7 +59,7 @@ afk_spawn()
elems = [];
elems[elems.size] = afk;
self thread scripts\sp\tws_common::destroy_on_disconnect(elems);
self thread scripts\sp\tws_t5_common::destroy_on_disconnect(elems);
}
afk_enter()
+1 -1
View File
@@ -1,4 +1,4 @@
/* tws_common.gsc — shared config, HUD helpers, connect/spawn/help registries. */
/* tws_t5_common.gsc — shared config, HUD helpers, connect/spawn/help registries. */
#include maps\_utility;
#include common_scripts\utility;
+18 -18
View File
@@ -1,4 +1,4 @@
/* tws_hud.gsc — bottom-left HUD stack. Timer / zombie count / health bar. */
/* tws_t5_hud.gsc — bottom-left HUD stack. Timer / zombie count / health bar. */
#include maps\_utility;
#include common_scripts\utility;
@@ -8,12 +8,12 @@ init()
if (GetDvar(#"zombiemode") != "1")
return;
scripts\sp\tws_common::ensure_config();
scripts\sp\tws_t5_common::ensure_config();
level thread round_watcher();
level thread game_over_watcher();
scripts\sp\tws_common::register_spawn_handler(::hud_start);
scripts\sp\tws_t5_common::register_spawn_handler(::hud_start);
}
hud_start()
@@ -87,18 +87,18 @@ hud_timer_row()
level notify("hud_match_started");
}
match = scripts\sp\tws_common::make_text_elem(
match = scripts\sp\tws_t5_common::make_text_elem(
self, level.hud_x, level.hud_row_match,
"left", level.hud_font_small, level.hud_c_dim);
match.alpha = 0; // revealed by match_timer_bind when round 1 opens
round = scripts\sp\tws_common::make_text_elem(
round = scripts\sp\tws_t5_common::make_text_elem(
self, level.hud_x, level.hud_row_round,
"left", level.hud_font_small, level.hud_c_dim);
round SetTimerUp(0);
// name only shows in co-op — your own name is noise in solo
name = scripts\sp\tws_common::make_text_elem(
name = scripts\sp\tws_t5_common::make_text_elem(
self, level.hud_x, level.hud_row_name,
"left", level.hud_font_small, level.hud_c_name);
@@ -113,7 +113,7 @@ hud_timer_row()
elems[elems.size] = match;
elems[elems.size] = round;
elems[elems.size] = name;
self thread scripts\sp\tws_common::destroy_on_disconnect(elems);
self thread scripts\sp\tws_t5_common::destroy_on_disconnect(elems);
self thread reset_timer_each_round(round);
self thread match_timer_bind(match);
@@ -177,22 +177,22 @@ freeze_timers_on_game_over(match_elem, round_elem)
level waittill("hud_game_over");
if (isDefined(match_elem) && isDefined(level.match_start_time))
match_elem SetText(scripts\sp\tws_common::format_mmss(getTime() - level.match_start_time));
match_elem SetText(scripts\sp\tws_t5_common::format_mmss(getTime() - level.match_start_time));
if (isDefined(round_elem) && isDefined(level.round_start_time))
round_elem SetText(scripts\sp\tws_common::format_mmss(getTime() - level.round_start_time));
round_elem SetText(scripts\sp\tws_t5_common::format_mmss(getTime() - level.round_start_time));
}
hud_zombie_counter()
{
self endon("disconnect");
label = scripts\sp\tws_common::make_text_elem(
label = scripts\sp\tws_t5_common::make_text_elem(
self, level.hud_x, level.hud_row_count,
"left", level.hud_font_large, level.hud_c_label);
label SetText("Zombies");
count = scripts\sp\tws_common::make_text_elem(
count = scripts\sp\tws_t5_common::make_text_elem(
self, level.hud_col_count, level.hud_row_count,
"left", level.hud_font_large, level.hud_c_count);
count SetValue(0);
@@ -200,7 +200,7 @@ hud_zombie_counter()
elems = [];
elems[elems.size] = label;
elems[elems.size] = count;
self thread scripts\sp\tws_common::destroy_on_disconnect(elems);
self thread scripts\sp\tws_t5_common::destroy_on_disconnect(elems);
last = -1;
@@ -245,12 +245,12 @@ hud_health_display()
bar_width = level.hud_bar_width;
// -2 on both axes so the back frames the bar with 2px padding
back = scripts\sp\tws_common::make_shader_elem(self, level.hud_x - 2, level.hud_row_bar - 2);
back = scripts\sp\tws_t5_common::make_shader_elem(self, level.hud_x - 2, level.hud_row_bar - 2);
back.alpha = 0.5;
back.color = (0, 0, 0);
back SetShader("black", bar_width + 4, level.hud_bar_height + 4);
bar = scripts\sp\tws_common::make_shader_elem(self, level.hud_x, level.hud_row_bar);
bar = scripts\sp\tws_t5_common::make_shader_elem(self, level.hud_x, level.hud_row_bar);
bar.color = level.hud_c_high;
bar SetShader("white", bar_width, level.hud_bar_height);
@@ -260,17 +260,17 @@ hud_health_display()
cur is right-aligned so digits grow leftward and the slash + max
never shift.
*/
cur = scripts\sp\tws_common::make_text_elem(
cur = scripts\sp\tws_t5_common::make_text_elem(
self, level.hud_col_health + 26, level.hud_row_bar + 3,
"right", level.hud_font_mid, level.hud_c_high);
cur SetValue(self.health);
slash = scripts\sp\tws_common::make_text_elem(
slash = scripts\sp\tws_t5_common::make_text_elem(
self, level.hud_col_health + 30, level.hud_row_bar + 3,
"center", level.hud_font_mid, level.hud_c_high);
slash SetText("/");
max_hp = scripts\sp\tws_common::make_text_elem(
max_hp = scripts\sp\tws_t5_common::make_text_elem(
self, level.hud_col_health + 34, level.hud_row_bar + 3,
"left", level.hud_font_mid, level.hud_c_high);
max_hp SetValue(self.maxhealth);
@@ -285,7 +285,7 @@ hud_health_display()
elems[elems.size] = cur;
elems[elems.size] = slash;
elems[elems.size] = max_hp;
self thread scripts\sp\tws_common::destroy_on_disconnect(elems);
self thread scripts\sp\tws_t5_common::destroy_on_disconnect(elems);
last_width = bar_width;
last_health = -1;
+4 -4
View File
@@ -1,4 +1,4 @@
/* tws_join.gsc - .join lets a newcomer leave spectator mode before round end. */
/* tws_t5_join.gsc - .join lets a newcomer leave spectator mode before round end. */
#include maps\_utility;
#include common_scripts\utility;
@@ -8,9 +8,9 @@ init()
if (GetDvar(#"zombiemode") != "1")
return;
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_t5_common::ensure_config();
scripts\sp\tws_t5_common::register_help_command(".join", "Join mid-round (newcomers only)");
scripts\sp\tws_t5_common::register_spawn_handler(::join_spawn);
level thread join_chat_listener();
}
@@ -1,4 +1,4 @@
/* tws_leaderboard.gsc - private map record announcement, once per connection. */
/* tws_t5_leaderboard.gsc - private map record announcement, once per connection. */
#include maps\_utility;
#include common_scripts\utility;
@@ -24,7 +24,7 @@ init()
level.tws_lb_pending = undefined;
level.tws_lb_cache = undefined;
level.tws_lb_attempt = 0;
scripts\sp\tws_common::register_connect_handler(::lb_welcome);
scripts\sp\tws_t5_common::register_connect_handler(::lb_welcome);
level thread lb_worker();
}
@@ -99,11 +99,11 @@ lb_show_hud(snapshot)
self endon("disconnect");
self endon("tws_lb_hud_done");
scripts\sp\tws_common::ensure_config();
scripts\sp\tws_t5_common::ensure_config();
// Match .help's position, typography and row spacing below the health bar.
y = level.hud_row_bar + level.hud_bar_height + 20;
elems = [];
title = scripts\sp\tws_common::make_text_elem(
title = scripts\sp\tws_t5_common::make_text_elem(
self, level.hud_x, y, "left", level.hud_font_mid, level.hud_c_afk);
title.alpha = 0;
if (snapshot.round == 0)
@@ -114,7 +114,7 @@ lb_show_hud(snapshot)
for (i = 0; i < 3; i++)
{
row = scripts\sp\tws_common::make_text_elem(
row = scripts\sp\tws_t5_common::make_text_elem(
self, level.hud_x, y + 22 + i * 18,
"left", level.hud_font_small, level.hud_c_count);
row.alpha = 0;
@@ -122,7 +122,7 @@ lb_show_hud(snapshot)
}
// Cleanup runs independently of the welcome thread's endon handlers.
self thread scripts\sp\tws_common::destroy_on_disconnect(elems);
self thread scripts\sp\tws_t5_common::destroy_on_disconnect(elems);
self thread lb_hud_end_game(elems, "end_game");
self thread lb_hud_end_game(elems, "game_ended");
self thread lb_hud_end_game(elems, "intermission");
@@ -303,7 +303,7 @@ lb_collect(request, pending)
lb_log(message)
{
print("[tws_leaderboard] " + message);
print("[tws_t5_leaderboard] " + message);
}
// Pure data helpers below are exercised by tools/check_leaderboard.mjs.
+3 -3
View File
@@ -1,5 +1,5 @@
/*
tws_lock.gsc — lobby g_password lock at milestone round. Existing players
tws_t5_lock.gsc — lobby g_password lock at milestone round. Existing players
are unaffected; g_password is only checked at connect. Cleared on
hud_game_over so the next lobby opens unlocked.
*/
@@ -12,14 +12,14 @@ init()
if (GetDvar(#"zombiemode") != "1")
return;
scripts\sp\tws_common::ensure_config();
scripts\sp\tws_t5_common::ensure_config();
lock_init();
}
lock_init()
{
level.lock_round = scripts\sp\tws_common::int_dvar_or("tws_lock_round", 20);
level.lock_round = scripts\sp\tws_t5_common::int_dvar_or("tws_lock_round", 20);
level.lock_active = false;
// recover from a prior lobby that crashed before clearing
@@ -1,4 +1,4 @@
/* tws_telemetry.gsc - Electric Cherry game reports via t5-gsc-utils. */
/* tws_t5_telemetry.gsc - Electric Cherry game reports via t5-gsc-utils. */
#include maps\_utility;
#include common_scripts\utility;
@@ -8,11 +8,11 @@ init()
if (GetDvar(#"zombiemode") != "1")
return;
scripts\sp\tws_common::ensure_config();
scripts\sp\tws_t5_common::ensure_config();
if (GetDvar("tws_telemetry_enabled") == "0")
{
print("[tws_telemetry] Disabled by tws_telemetry_enabled=0");
print("[tws_t5_telemetry] Disabled by tws_telemetry_enabled=0");
return;
}
@@ -25,7 +25,7 @@ init()
if (getSubStr(toLower(level.telemetry_url), 0, 8) != "https://")
{
print("^1[tws_telemetry] Telemetry disabled: tws_telemetry_url must use HTTPS");
print("^1[tws_t5_telemetry] Telemetry disabled: tws_telemetry_url must use HTTPS");
return;
}
@@ -34,14 +34,14 @@ init()
level.telemetry_token = "e03a03f7d22418f0c062b0b2a7317fd91b1194c4400e84b469866de501180dc2";
if (level.telemetry_token.size < 32)
{
print("^1[tws_telemetry] Telemetry disabled: embedded telemetry token must be at least 32 characters");
print("^1[tws_t5_telemetry] Telemetry disabled: embedded telemetry token must be at least 32 characters");
return;
}
level.telemetry_server = GetDvar("tws_server_id");
if (level.telemetry_server == "")
{
print("^1[tws_telemetry] Set tws_server_id to the registered Electric Cherry server name");
print("^1[tws_t5_telemetry] Set tws_server_id to the registered Electric Cherry server name");
return;
}
@@ -66,7 +66,7 @@ init()
telemetry_log(1, "Initialized; waiting for match start. log_level=" + telemetry_log_level());
// Use the shared dispatcher; a second connected listener breaks T5 SP.
scripts\sp\tws_common::register_spawn_handler(::telemetry_game_start);
scripts\sp\tws_t5_common::register_spawn_handler(::telemetry_game_start);
level thread telemetry_game_end_watcher();
level thread telemetry_sender();
@@ -367,7 +367,7 @@ telemetry_log(verbosity, message)
if (verbosity > telemetry_log_level())
return;
print("[tws_telemetry] server=" + level.telemetry_server + " map=" + GetDvar("mapname") + " time_ms=" + getTime() + " " + message);
print("[tws_t5_telemetry] server=" + level.telemetry_server + " map=" + GetDvar("mapname") + " time_ms=" + getTime() + " " + message);
}
// Escape newlines/control characters and bound response/error output. Do not
+4 -5
View File
@@ -1,8 +1,7 @@
# T6 port notes
Baseline: the current files in `C:\Users\josh1\Documents\Code\TWS-T5-AIO`.
The T5 originals were not modified. DB, bank, their schema and the added example
config have been removed from this project.
T6 is ported from T5. Both source trees contain common, HUD, AFK, join, lock,
telemetry and leaderboard modules; neither includes bank or database modules.
## Differences retained for T6 and requested fixes
@@ -16,7 +15,7 @@ config have been removed from this project.
| Timers | Seed elapsed time for late joiners; stop round watcher/reset on game over | T6 timer behavior; keep frozen timers frozen |
| AFK | Maintain control freeze, disable guns/grenades, guard player state and stale grace threads | Requested movement/shooting fix; retain `.afk` / `.back` and two-second grace |
| Telemetry | Global `httpPost(url, options)`; T5-parity queue + heartbeat + game_start/end + watchdog + log-level dvar; per-player `guid` sources from Plutonium's `player.guid` engine property (T5 uses `player getGuid()` method); per-match `kills`/`headshots`/`downs`/`revives` fall back to `0` when `player.stats[]` is unpopulated | T6 has no `getGuid` method but exposes the persistent identity as `.guid` on the player entity; `_zm_stats` writes only into persistent stat storage, so `player.stats[]` is not a stock T6 field. Payload keys match T5 for downstream shape parity |
| Join | Port of T5 `tws_join.gsc`; uses `level.spawnplayer` / `level.custom_spawnplayer` (lowercase) and `maps\mp\zombies\_zm::spectator_respawn`; readiness gated on `self.sessionstate == "spectator"` instead of T5's `self.initialized` | T6 stock spectator respawn hook lives in `_zm.gsc`; `self.initialized` is a T5 SP zombiemode marker that T6 stock does not populate the same way |
| Join | Port of T5 `tws_t5_join.gsc`; uses `level.spawnplayer` / `level.custom_spawnplayer` (lowercase) and `maps\mp\zombies\_zm::spectator_respawn`; readiness gated on `self.sessionstate == "spectator"` instead of T5's `self.initialized` | T6 stock spectator respawn hook lives in `_zm.gsc`; `self.initialized` is a T5 SP zombiemode marker that T6 stock does not populate the same way |
Common retains the T5 HUD constants, helpers and first-spawn dispatcher.
HUD again owns the `hud_game_over` notification fan-out. Lock again waits on HUD
@@ -46,7 +45,7 @@ not an external configuration system.
## Leaderboard join announcements
The module is `tws_t6_leaderboard.gsc` in T6 and `tws_leaderboard.gsc` in T5.
The module is `tws_t6_leaderboard.gsc` in T6 and `tws_t5_leaderboard.gsc` in T5.
Common's existing
connection listener now also dispatches optional connection handlers after a
one-frame yield. Leaderboard waits for player readiness and uses a level-owned
+4 -4
View File
@@ -42,7 +42,7 @@ function functions(source) {
}
function engine(game) {
const source = readFileSync(new URL(`${game}/${game === 't6' ? 'tws_t6_leaderboard' : 'tws_leaderboard'}.gsc`, root), 'utf8');
const source = readFileSync(new URL(`${game}/tws_${game}_leaderboard.gsc`, root), 'utf8');
const all = functions(source);
const excluded = new Set(['lb_worker', 'lb_welcome', 'lb_get', 'lb_collect', 'lb_log', 'lb_show_hud', 'lb_hud_help_visible', 'lb_hud_end_game', 'lb_hud_destroy']);
const table = [];
@@ -89,7 +89,7 @@ for (const game of ['t5', 't6']) {
const { context: c, source, all, dvars, responses, queries } = engine(game);
const option = mapOption(game);
check(`${game} exactly one connect listener across modules`, () => {
const common = readFileSync(new URL(game === 't5' ? 't5/tws_common.gsc' : 't6/tws_t6_common.gsc', root), 'utf8');
const common = readFileSync(new URL(`${game}/tws_${game}_common.gsc`, root), 'utf8');
assert.equal((common.match(/waittill\("connected"/g) ?? []).length, 1);
assert(!source.includes('waittill("connected"'));
assert(!all.get('lb_welcome').includes('spawned_player'));
@@ -212,11 +212,11 @@ for (const game of ['t5', 't6']) {
assert.equal(responses.length, 0, `${game} unused responses`);
}
check('paired data helpers stay identical', () => {
const t5 = functions(readFileSync(new URL('t5/tws_leaderboard.gsc', root), 'utf8'));
const t5 = functions(readFileSync(new URL('t5/tws_t5_leaderboard.gsc', root), 'utf8'));
const t6 = functions(readFileSync(new URL('t6/tws_t6_leaderboard.gsc', root), 'utf8'));
for (const [name, fn] of t5) {
if (!['lb_selection', 'lb_welcome', 'lb_get', 'lb_log'].includes(name)) {
const normalized = t6.get(name).replaceAll('scripts\\zm\\tws_t6_common::', 'scripts\\sp\\tws_common::');
const normalized = t6.get(name).replaceAll('scripts\\zm\\tws_t6_common::', 'scripts\\sp\\tws_t5_common::');
assert.equal(normalized, fn, name);
}
}