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 ## 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). - [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 ## Parity
@@ -15,21 +15,17 @@ Modules currently in each tree:
| Module | T5 | T6 | | Module | T5 | T6 |
|------------|-------------------------------|---------------------------------| |------------|-------------------------------|---------------------------------|
| common | `t5/tws_common.gsc` | `t6/tws_t6_common.gsc` | | common | `t5/tws_t5_common.gsc` | `t6/tws_t6_common.gsc` |
| hud | `t5/tws_hud.gsc` | `t6/tws_t6_hud.gsc` | | hud | `t5/tws_t5_hud.gsc` | `t6/tws_t6_hud.gsc` |
| afk | `t5/tws_afk.gsc` | `t6/tws_t6_afk.gsc` | | afk | `t5/tws_t5_afk.gsc` | `t6/tws_t6_afk.gsc` |
| lock | `t5/tws_lock.gsc` | `t6/tws_t6_lock.gsc` | | lock | `t5/tws_t5_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 | | telemetry | `t5/tws_t5_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` | | join | `t5/tws_t5_join.gsc` | `t6/tws_t6_join.gsc` |
| leaderboard | `t5/tws_leaderboard.gsc` | `t6/tws_t6_leaderboard.gsc` | | leaderboard | `t5/tws_t5_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.
## Map record announcements ## 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. **along with the updated common module** for that game.
The new module privately announces the current map's fleet-wide highest recorded The new module privately announces the current map's fleet-wide highest recorded
round and every tied holder once per connection, including spectator newcomers. 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. load-bearing decisions that show up as invariants in the code.
## Install ## 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/`). (v2 Plutainer: `app/runtime/plutonium/storage/t5/scripts/sp/`).
- `sudo chown -R 1000:1000 <app>` — Plutainer runs as UID 1000 - `sudo chown -R 1000:1000 <app>` — Plutainer runs as UID 1000
- `docker exec <container> rcon-cli map_restart` — reloads scripts in place - `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) ## Platform quirks (T5 SP engine, Plutonium)
- **Initialize locals before loops when reading them afterwards.** T5 rejected - **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 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. 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. 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 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. `register_connect_handler` for spectator-safe work after the dispatcher yields.
Leaderboard uses the latter; its level worker owns all HTTP work. Leaderboard uses the latter; its level worker owns all HTTP work.
- **Chat uses `level waittill("say", ...)`**, not `callbackPlayerSay`. T5 SP - **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 - **Zombies namespace is WaW-derived** (`maps\_zombiemode_*`), not BO2's
`maps\mp\zombies\_zm_*`. `maps\mp\zombies\_zm_*`.
- **Check `t5-gsc-utils` source as well as its README.** Current source - **Check `t5-gsc-utils` source as well as its README.** Current source
includes `http::request` and `jsonSerialize` / `jsonParse`, in addition includes `http::request` and `jsonSerialize` / `jsonParse`. Telemetry and
to file IO, console commands and MySQL. Telemetry requires a DLL built leaderboard require a DLL built with these HTTP/JSON components; older
with these HTTP/JSON components; older builds may lack them. builds may lack them.
See [HTTP source](https://github.com/alicealys/t5-gsc-utils/blob/main/src/component/http.cpp) 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). 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 is intercepted by the zombiemode scripts before it reaches our scope, and
polling `self.health` misses the zero-crossing (health is rewritten during polling `self.health` misses the zero-crossing (health is rewritten during
the game-over sequence). 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 ## Module map
| File | Role | Fires notifies | Reads notifies | | File | Role | Fires notifies | Reads notifies |
|---|---|---|---| |---|---|---|---|
| `tws_common` | Shared config, HUD helpers, `.help` registry, spawn-handler registry, single connect listener + dispatcher | — | `say` | | `tws_t5_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_t5_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_t5_afk` | `.afk` / `.back` chat toggle, owns its [AFK] indicator | — | — |
| `tws_bank` | Persistent bank + `bank` / `deposit` / `withdraw` commands | — | `db_probe_done` | | `tws_t5_join` | `.join` for spectators who have not played on this connection | — | `say`, `end_game`, `game_ended`, `intermission` |
| `tws_afk` | `.afk` / `.back` chat toggle, owns its [AFK] indicator | — | — | | `tws_t5_lock` | Lobby `g_password` lock at milestone round (`tws_lock_round`, default 20) | — | `hud_round_changed`, `hud_game_over` |
| `tws_join` | `.join` for spectators who have not played on this connection | — | `say`, `end_game`, `game_ended`, `intermission` | | `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_lock` | Lobby `g_password` lock at milestone round (`tws_lock_round`, default 20) | — | `hud_round_changed`, `hud_game_over` | | `tws_t5_leaderboard` | Cached completed-match map record and all tied holders, privately on join | — | `end_game`, `game_ended`, `intermission` |
| `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` |
+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 Custom HUD + AFK toggle + immediate join + lobby lock + game telemetry + map records for Plutonium T5
(Black Ops 1) zombies dedicated servers. Split across nine `.gsc` files, (Black Ops 1) zombies dedicated servers. Split across seven `.gsc` files,
each installable / removable in isolation (with `tws_common` as the shared with `tws_t5_common` as the shared hub. Lobby lock also requires the HUD module.
hub).
Platform quirks and deploy commands: [NOTES.md](NOTES.md). Platform quirks and deploy commands: [NOTES.md](NOTES.md).
@@ -11,19 +10,30 @@ Platform quirks and deploy commands: [NOTES.md](NOTES.md).
| File | Role | | File | Role |
|---|---| |---|---|
| [tws_common.gsc](tws_common.gsc) | Shared config, HUD helpers, `.help`, connect/spawn registries, single `connected` listener + dispatcher | | [tws_t5_common.gsc](tws_t5_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_t5_hud.gsc](tws_t5_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_t5_afk.gsc](tws_t5_afk.gsc) | `.afk` / `.back` chat toggle + `[AFK]` indicator |
| [tws_bank.gsc](tws_bank.gsc) | Persistent per-player bank + console commands | | [tws_t5_join.gsc](tws_t5_join.gsc) | `.join` lets newcomers spawn before the current round ends |
| [tws_afk.gsc](tws_afk.gsc) | `.afk` / `.back` chat toggle + `[AFK]` indicator | | [tws_t5_lock.gsc](tws_t5_lock.gsc) | Lobby `g_password` lock at a milestone round |
| [tws_join.gsc](tws_join.gsc) | `.join` lets newcomers spawn before the current round ends | | [tws_t5_telemetry.gsc](tws_t5_telemetry.gsc) | Direct HTTP game start, round, heartbeat, and game end reports |
| [tws_lock.gsc](tws_lock.gsc) | Lobby `g_password` lock at a milestone round | | [tws_t5_leaderboard.gsc](tws_t5_leaderboard.gsc) | Private map high-round record and all tied holders on connection |
| [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 | ## 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 ## 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 `storage/t5/scripts/sp/`, then restart the map. Leaderboard requires a
[t5-gsc-utils](https://github.com/alicealys/t5-gsc-utils) build exposing [t5-gsc-utils](https://github.com/alicealys/t5-gsc-utils) build exposing
`http::request`, `jsonParse`, `typeof` and `formatString`. A plugin upgrade `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. the enable dvar does not bypass native linking dependencies.
Records are read from the public Electric Cherry API with no credentials and 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 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) the API's map list. [Shared configuration, caching and validation](../README.md#map-record-announcements)
apply to both games. 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) | | `.join` | Join mid-round (newcomers only) |
Each module registers its own help entries; removing AFK or join also removes 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 those entries from `.help` after a map restart.
`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
`scripts\sp\tws_common::...`, dashed means the source waits on a level `scripts\sp\tws_t5_common::...`, dashed means the source waits on a level
notify fired by the target. Transitive edges are elided (so `tws_bank` notify fired by the target.
and `tws_lock` reach `tws_common` through `tws_db` / `tws_hud` on the
graph, but they also call `tws_common` directly).
```mermaid ```mermaid
graph BT 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_t5_hud]
hud[tws_hud] afk[tws_t5_afk]
afk[tws_afk] join[tws_t5_join]
join[tws_join] lock[tws_t5_lock]
bank[tws_bank] telemetry[tws_t5_telemetry]
lock[tws_lock] leaderboard[tws_t5_leaderboard]
telemetry[tws_telemetry]
leaderboard[tws_leaderboard]
db --> common
hud --> common hud --> common
afk --> common afk --> common
join --> common join --> common
lock --> common
telemetry --> common telemetry --> common
leaderboard --> common leaderboard --> common
bank -. db_probe_done .-> db
lock -. hud_round_changed<br/>hud_game_over .-> hud 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 - 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 `hud_game_over`) or through spawn handlers registered with
`tws_common::register_spawn_handler` / `register_connect_handler`. `tws_t5_common::register_spawn_handler` / `register_connect_handler`.
- Any file except `tws_common` can be removed; the rest still load. - Any file except `tws_t5_common` can be removed; the rest still load.
`tws_afk` has no downstream consumers — pull it independently. `tws_t5_afk` has no downstream consumers — pull it independently.
`tws_bank` needs `tws_db`; `tws_lock` needs `tws_hud`. `tws_t5_lock` needs `tws_t5_hud` to receive round and game-over notifications.
## Runtime notifies ## Runtime notifies
| Notify | Fired by | Read by | | Notify | Fired by | Read by |
|---|---|---| |---|---|---|
| `db_probe_done` | `tws_db` | `tws_bank` (via `bank_deferred_start`) | | `hud_round_changed` | `tws_t5_hud` | `tws_t5_hud` (round-timer reset), `tws_t5_lock` |
| `hud_round_changed` | `tws_hud` | `tws_hud` (round-timer reset), `tws_lock` | | `hud_match_started` | `tws_t5_hud` | `tws_t5_hud` (match-timer reveal) |
| `hud_match_started` | `tws_hud` | `tws_hud` (match-timer reveal) | | `hud_game_over` | `tws_t5_hud` | `tws_t5_hud` (freeze timers), `tws_t5_lock` (clear password) |
| `hud_game_over` | `tws_hud` | `tws_hud` (freeze timers), `tws_lock` (clear password) |
--- ---
## tws_common.gsc ## tws_t5_common.gsc
Hub file. Shared config, HUD helpers, spawn-handler registry, and the Hub file. Shared config, HUD helpers, spawn-handler registry, and the
single `level waittill("connected")` in the fleet. Adding a second 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 ## tws_t5_hud.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
Bottom-left cluster. Per-player elements spawn via `::hud_start`. 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`. `hud_zombie_counter`, `hud_health_display`.
**Depends on** **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`, `make_shader_elem`, `destroy_on_disconnect`, `format_mmss`,
`register_spawn_handler` `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`, - Stock BO1 state: `level.round_number`, `level.zombie_total`,
`level.players`, `self.health`, `self.maxhealth`, `self.name` / `level.players`, `self.health`, `self.maxhealth`, `self.name` /
`self.playername`, AI entities' `.animname` and `.ignore_enemy_count` `self.playername`, AI entities' `.animname` and `.ignore_enemy_count`
--- ---
## tws_bank.gsc ## tws_t5_afk.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
Self-invoked pause mode. Owns the `[AFK]` indicator element entirely — HUD Self-invoked pause mode. Owns the `[AFK]` indicator element entirely — HUD
module has zero knowledge of AFK. module has zero knowledge of AFK.
@@ -271,7 +211,7 @@ module has zero knowledge of AFK.
- Level var: `level.afk_grace_seconds` (default 2) - Level var: `level.afk_grace_seconds` (default 2)
**Depends on** **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` `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`
@@ -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 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** **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** **Features**
- Sets `g_password` to a random per-lobby value when the round crosses a - 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 | | `level.lock_active` | `true` after the password has been set |
**Depends on** **Depends on**
- `scripts\sp\tws_common::` — `ensure_config`, `int_dvar_or` - `scripts\sp\tws_t5_common::` — `ensure_config`, `int_dvar_or`
- `tws_hud` — waits on `hud_round_changed` and `hud_game_over` - `tws_t5_hud` — waits on `hud_round_changed` and `hud_game_over`
- Stock BO1: `level.round_number` - Stock BO1: `level.round_number`
- Dvar: `tws_lock_round` (int, default `20`) - 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 | | Dvar | Consumer | Default | Purpose |
|---|---|---|---| |---|---|---|---|
| `zombiemode` | every module | — | Gate: every `init()` returns early unless `"1"` | | `zombiemode` | every module | — | Gate: every `init()` returns early unless `"1"` |
| `tws_lock_round` | `tws_lock` | `20` | Round at which the lobby locks | | `tws_lock_round` | `tws_t5_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_server_id` | `tws_t5_telemetry` | Required for telemetry | Exact registered Electric Cherry server name |
| `tws_telemetry_enabled` | `tws_telemetry` | `1` | Set to `0` to disable telemetry at init | | `tws_telemetry_enabled` | `tws_t5_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_url` | `tws_t5_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_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 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/`. `/api/telemetry`. Install alongside `tws_t5_common.gsc` in `storage/t5/scripts/sp/`.
No HUD or database dependency. Reports run whether or not a beta is active. No HUD dependency. Reports run whether or not a beta is active.
Follow the shared [authentication setup](../README.md#telemetry-authentication-t5-and-t6) Follow the shared [authentication setup](../README.md#telemetry-authentication-t5-and-t6)
to configure the service before upgrading. The reporter token is embedded in 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 `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 4. Delay HTTP completion while rounds advance and the match ends. Verify
requests remain ordered and retain the stats captured at each event. requests remain ordered and retain the stats captured at each event.
With the API unreachable, verify transport-error logging and continued 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 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`. 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, 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 ## External dependencies
- **[t5-gsc-utils](https://github.com/alicealys/t5-gsc-utils)** — required. [t5-gsc-utils](https://github.com/alicealys/t5-gsc-utils) is required for telemetry
`tws_db` and `tws_bank` need the `mysql::` namespace; `tws_bank` also and leaderboard HTTP, JSON, type and string helpers. Install a build exposing
needs `command::add_sv`. the natives listed in each module's documentation, then restart the server
- **MariaDB / MySQL server** reachable from the Plutonium container as process. No database is required.
`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.
-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 Chat uses level waittill("say") — callbackPlayerSay never fires on T5 SP
dedicated (see NOTES.md). dedicated (see NOTES.md).
*/ */
@@ -12,13 +12,13 @@ init()
if (GetDvar(#"zombiemode") != "1") if (GetDvar(#"zombiemode") != "1")
return; return;
scripts\sp\tws_common::ensure_config(); scripts\sp\tws_t5_common::ensure_config();
scripts\sp\tws_common::register_help_command(".afk", "Pause with protection"); scripts\sp\tws_t5_common::register_help_command(".afk", "Pause with protection");
scripts\sp\tws_common::register_help_command(".back", "Return from AFK"); scripts\sp\tws_t5_common::register_help_command(".back", "Return from AFK");
afk_init(); afk_init();
scripts\sp\tws_common::register_spawn_handler(::afk_spawn); scripts\sp\tws_t5_common::register_spawn_handler(::afk_spawn);
} }
afk_init() afk_init()
@@ -50,7 +50,7 @@ afk_spawn()
{ {
self endon("disconnect"); 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, self, level.hud_x + 42, level.hud_row_match,
"left", level.hud_font_small, level.hud_c_afk); "left", level.hud_font_small, level.hud_c_afk);
afk SetText("[AFK]"); afk SetText("[AFK]");
@@ -59,7 +59,7 @@ afk_spawn()
elems = []; elems = [];
elems[elems.size] = afk; 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() 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 maps\_utility;
#include common_scripts\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 maps\_utility;
#include common_scripts\utility; #include common_scripts\utility;
@@ -8,12 +8,12 @@ init()
if (GetDvar(#"zombiemode") != "1") if (GetDvar(#"zombiemode") != "1")
return; return;
scripts\sp\tws_common::ensure_config(); scripts\sp\tws_t5_common::ensure_config();
level thread round_watcher(); level thread round_watcher();
level thread game_over_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() hud_start()
@@ -87,18 +87,18 @@ hud_timer_row()
level notify("hud_match_started"); 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, self, level.hud_x, level.hud_row_match,
"left", level.hud_font_small, level.hud_c_dim); "left", level.hud_font_small, level.hud_c_dim);
match.alpha = 0; // revealed by match_timer_bind when round 1 opens 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, self, level.hud_x, level.hud_row_round,
"left", level.hud_font_small, level.hud_c_dim); "left", level.hud_font_small, level.hud_c_dim);
round SetTimerUp(0); round SetTimerUp(0);
// name only shows in co-op — your own name is noise in solo // 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, self, level.hud_x, level.hud_row_name,
"left", level.hud_font_small, level.hud_c_name); "left", level.hud_font_small, level.hud_c_name);
@@ -113,7 +113,7 @@ hud_timer_row()
elems[elems.size] = match; elems[elems.size] = match;
elems[elems.size] = round; elems[elems.size] = round;
elems[elems.size] = name; 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 reset_timer_each_round(round);
self thread match_timer_bind(match); self thread match_timer_bind(match);
@@ -177,22 +177,22 @@ freeze_timers_on_game_over(match_elem, round_elem)
level waittill("hud_game_over"); level waittill("hud_game_over");
if (isDefined(match_elem) && isDefined(level.match_start_time)) 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)) 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() hud_zombie_counter()
{ {
self endon("disconnect"); 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, self, level.hud_x, level.hud_row_count,
"left", level.hud_font_large, level.hud_c_label); "left", level.hud_font_large, level.hud_c_label);
label SetText("Zombies"); 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, self, level.hud_col_count, level.hud_row_count,
"left", level.hud_font_large, level.hud_c_count); "left", level.hud_font_large, level.hud_c_count);
count SetValue(0); count SetValue(0);
@@ -200,7 +200,7 @@ hud_zombie_counter()
elems = []; elems = [];
elems[elems.size] = label; elems[elems.size] = label;
elems[elems.size] = count; 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; last = -1;
@@ -245,12 +245,12 @@ hud_health_display()
bar_width = level.hud_bar_width; bar_width = level.hud_bar_width;
// -2 on both axes so the back frames the bar with 2px padding // -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.alpha = 0.5;
back.color = (0, 0, 0); back.color = (0, 0, 0);
back SetShader("black", bar_width + 4, level.hud_bar_height + 4); 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.color = level.hud_c_high;
bar SetShader("white", bar_width, level.hud_bar_height); 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 cur is right-aligned so digits grow leftward and the slash + max
never shift. 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, self, level.hud_col_health + 26, level.hud_row_bar + 3,
"right", level.hud_font_mid, level.hud_c_high); "right", level.hud_font_mid, level.hud_c_high);
cur SetValue(self.health); 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, self, level.hud_col_health + 30, level.hud_row_bar + 3,
"center", level.hud_font_mid, level.hud_c_high); "center", level.hud_font_mid, level.hud_c_high);
slash SetText("/"); 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, self, level.hud_col_health + 34, level.hud_row_bar + 3,
"left", level.hud_font_mid, level.hud_c_high); "left", level.hud_font_mid, level.hud_c_high);
max_hp SetValue(self.maxhealth); max_hp SetValue(self.maxhealth);
@@ -285,7 +285,7 @@ hud_health_display()
elems[elems.size] = cur; elems[elems.size] = cur;
elems[elems.size] = slash; elems[elems.size] = slash;
elems[elems.size] = max_hp; 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_width = bar_width;
last_health = -1; 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 maps\_utility;
#include common_scripts\utility; #include common_scripts\utility;
@@ -8,9 +8,9 @@ init()
if (GetDvar(#"zombiemode") != "1") if (GetDvar(#"zombiemode") != "1")
return; return;
scripts\sp\tws_common::ensure_config(); scripts\sp\tws_t5_common::ensure_config();
scripts\sp\tws_common::register_help_command(".join", "Join mid-round (newcomers only)"); scripts\sp\tws_t5_common::register_help_command(".join", "Join mid-round (newcomers only)");
scripts\sp\tws_common::register_spawn_handler(::join_spawn); scripts\sp\tws_t5_common::register_spawn_handler(::join_spawn);
level thread join_chat_listener(); 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 maps\_utility;
#include common_scripts\utility; #include common_scripts\utility;
@@ -24,7 +24,7 @@ init()
level.tws_lb_pending = undefined; level.tws_lb_pending = undefined;
level.tws_lb_cache = undefined; level.tws_lb_cache = undefined;
level.tws_lb_attempt = 0; 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(); level thread lb_worker();
} }
@@ -99,11 +99,11 @@ lb_show_hud(snapshot)
self endon("disconnect"); self endon("disconnect");
self endon("tws_lb_hud_done"); 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. // Match .help's position, typography and row spacing below the health bar.
y = level.hud_row_bar + level.hud_bar_height + 20; y = level.hud_row_bar + level.hud_bar_height + 20;
elems = []; 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); self, level.hud_x, y, "left", level.hud_font_mid, level.hud_c_afk);
title.alpha = 0; title.alpha = 0;
if (snapshot.round == 0) if (snapshot.round == 0)
@@ -114,7 +114,7 @@ lb_show_hud(snapshot)
for (i = 0; i < 3; i++) 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, self, level.hud_x, y + 22 + i * 18,
"left", level.hud_font_small, level.hud_c_count); "left", level.hud_font_small, level.hud_c_count);
row.alpha = 0; row.alpha = 0;
@@ -122,7 +122,7 @@ lb_show_hud(snapshot)
} }
// Cleanup runs independently of the welcome thread's endon handlers. // 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, "end_game");
self thread lb_hud_end_game(elems, "game_ended"); self thread lb_hud_end_game(elems, "game_ended");
self thread lb_hud_end_game(elems, "intermission"); self thread lb_hud_end_game(elems, "intermission");
@@ -303,7 +303,7 @@ lb_collect(request, pending)
lb_log(message) lb_log(message)
{ {
print("[tws_leaderboard] " + message); print("[tws_t5_leaderboard] " + message);
} }
// Pure data helpers below are exercised by tools/check_leaderboard.mjs. // 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 are unaffected; g_password is only checked at connect. Cleared on
hud_game_over so the next lobby opens unlocked. hud_game_over so the next lobby opens unlocked.
*/ */
@@ -12,14 +12,14 @@ init()
if (GetDvar(#"zombiemode") != "1") if (GetDvar(#"zombiemode") != "1")
return; return;
scripts\sp\tws_common::ensure_config(); scripts\sp\tws_t5_common::ensure_config();
lock_init(); lock_init();
} }
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; level.lock_active = false;
// recover from a prior lobby that crashed before clearing // 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 maps\_utility;
#include common_scripts\utility; #include common_scripts\utility;
@@ -8,11 +8,11 @@ init()
if (GetDvar(#"zombiemode") != "1") if (GetDvar(#"zombiemode") != "1")
return; return;
scripts\sp\tws_common::ensure_config(); scripts\sp\tws_t5_common::ensure_config();
if (GetDvar("tws_telemetry_enabled") == "0") 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; return;
} }
@@ -25,7 +25,7 @@ init()
if (getSubStr(toLower(level.telemetry_url), 0, 8) != "https://") 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; return;
} }
@@ -34,14 +34,14 @@ init()
level.telemetry_token = "e03a03f7d22418f0c062b0b2a7317fd91b1194c4400e84b469866de501180dc2"; level.telemetry_token = "e03a03f7d22418f0c062b0b2a7317fd91b1194c4400e84b469866de501180dc2";
if (level.telemetry_token.size < 32) 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; return;
} }
level.telemetry_server = GetDvar("tws_server_id"); level.telemetry_server = GetDvar("tws_server_id");
if (level.telemetry_server == "") 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; return;
} }
@@ -66,7 +66,7 @@ init()
telemetry_log(1, "Initialized; waiting for match start. log_level=" + telemetry_log_level()); telemetry_log(1, "Initialized; waiting for match start. log_level=" + telemetry_log_level());
// Use the shared dispatcher; a second connected listener breaks T5 SP. // 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_game_end_watcher();
level thread telemetry_sender(); level thread telemetry_sender();
@@ -367,7 +367,7 @@ telemetry_log(verbosity, message)
if (verbosity > telemetry_log_level()) if (verbosity > telemetry_log_level())
return; 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 // Escape newlines/control characters and bound response/error output. Do not
+4 -5
View File
@@ -1,8 +1,7 @@
# T6 port notes # T6 port notes
Baseline: the current files in `C:\Users\josh1\Documents\Code\TWS-T5-AIO`. T6 is ported from T5. Both source trees contain common, HUD, AFK, join, lock,
The T5 originals were not modified. DB, bank, their schema and the added example telemetry and leaderboard modules; neither includes bank or database modules.
config have been removed from this project.
## Differences retained for T6 and requested fixes ## 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 | | 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 | | 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 | | 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. 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 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 ## 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 Common's existing
connection listener now also dispatches optional connection handlers after a connection listener now also dispatches optional connection handlers after a
one-frame yield. Leaderboard waits for player readiness and uses a level-owned 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) { 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 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 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 = []; const table = [];
@@ -89,7 +89,7 @@ for (const game of ['t5', 't6']) {
const { context: c, source, all, dvars, responses, queries } = engine(game); const { context: c, source, all, dvars, responses, queries } = engine(game);
const option = mapOption(game); const option = mapOption(game);
check(`${game} exactly one connect listener across modules`, () => { 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.equal((common.match(/waittill\("connected"/g) ?? []).length, 1);
assert(!source.includes('waittill("connected"')); assert(!source.includes('waittill("connected"'));
assert(!all.get('lb_welcome').includes('spawned_player')); 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`); assert.equal(responses.length, 0, `${game} unused responses`);
} }
check('paired data helpers stay identical', () => { 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')); const t6 = functions(readFileSync(new URL('t6/tws_t6_leaderboard.gsc', root), 'utf8'));
for (const [name, fn] of t5) { for (const [name, fn] of t5) {
if (!['lb_selection', 'lb_welcome', 'lb_get', 'lb_log'].includes(name)) { 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); assert.equal(normalized, fn, name);
} }
} }