146 lines
8.5 KiB
Markdown
146 lines
8.5 KiB
Markdown
# Player bank
|
||
|
||
The bank stores one points balance per exact GUID across T5 and T6. Players use
|
||
`.b`, `.d <amount|all>` and `.w <amount|all>` with the TWS bank scripts. Deposits
|
||
move spendable match points into the bank; withdrawals return bank points to
|
||
the current match. Neither changes earned `score_total`. Bank-assisted games
|
||
still count toward the existing leaderboards; the ledger records their match
|
||
IDs. This release does not introduce separate bank-assisted rankings.
|
||
|
||
## Setup and rollout
|
||
|
||
1. Back up the controller database. Deploy Electric Cherry first and restart
|
||
the controller. Migration 20 creates accounts, transfer requests and a ledger.
|
||
2. Generate a **different** secret for each registered game server, for example
|
||
`ec_bank_` followed by `openssl rand -hex 32`. Configure `EC_BANK_TOKENS` as a
|
||
JSON object mapping exact server names to these secrets. Docker Compose
|
||
forwards this environment variable. An example shape with placeholders is
|
||
`{"t5_zombie_theater_1":"ec_bank_<random-secret>"}`.
|
||
3. Set `EC_BANK_MAX_BALANCE` (default 250000, range 1–1000000). Invalid configuration
|
||
refuses new deposits. A limit reduction does not confiscate existing balances
|
||
or invalidate already prepared deposits; withdrawals remain available.
|
||
4. Build/install TWS plugins `t5-plugin`/`t6-plugin` 1.1.0 or later and restart
|
||
the **whole game-server process**. Install `tws_t5_bank.gsc` or
|
||
`tws_t6_bank.gsc` 1.0.0, the game's common module (1.0.0+) and telemetry
|
||
reporter (1.0.0+). Restart the map after installing scripts/configuration.
|
||
5. In each server's private configuration, set `tws_server_id` to its registered
|
||
name and `tws_bank_token` to that server's secret. The optional `tws_bank_url`
|
||
defaults to `https://ec.thewrightserver.net/api/bank`; overrides must use
|
||
HTTPS. Keep these settings out of shared scripts and public config downloads.
|
||
|
||
Bank tokens must match `ec_bank_` followed by 32–128 URL-safe letters, digits,
|
||
underscores or hyphens. Missing, malformed, duplicate, or telemetry-reused tokens
|
||
disable authentication. `EC_TELEMETRY_TOKEN`, host tokens and browser sessions
|
||
cannot authorize bank requests. The credential determines the server, and a
|
||
different `server` in the body is refused. Rotate a token by updating both the
|
||
controller mapping and that server's private configuration, then restart them.
|
||
Keep the same server name and bank URL so existing decisions can finish.
|
||
|
||
## Machine API
|
||
|
||
All routes below are `POST /api/bank/<action>`, with `Content-Type:
|
||
application/json` and `Authorization: Bearer <server-bank-token>`. Responses
|
||
are never cached. Bodies are capped at 4096 bytes. GUIDs and request IDs stay
|
||
strings; never convert them to numbers.
|
||
|
||
| Action | Body | Meaning |
|
||
| ---------- | ------------------------- | -------------------------------------------------------------------- |
|
||
| `balance` | `server`, `guid` | Read the shared balance and currently available points |
|
||
| `deposit` | Transfer fields below | Reserve deposit capacity without crediting the account yet |
|
||
| `withdraw` | Transfer fields below | Reserve bank funds without debiting the account yet |
|
||
| `commit` | Identical transfer fields | Apply a prepared transfer once and append its ledger entry |
|
||
| `cancel` | Identical transfer fields | Release a reservation; also prevents a delayed prepare from applying |
|
||
|
||
Transfer example:
|
||
|
||
```json
|
||
{
|
||
"server": "t5_zombie_theater_1",
|
||
"guid": "18446744073709551615",
|
||
"match": "reporter-match-key",
|
||
"request_id": "unique-process-token-17",
|
||
"operation": "deposit",
|
||
"amount": 2000,
|
||
"capacity": 995000
|
||
}
|
||
```
|
||
|
||
`match` is the reporter's telemetry match key, **not** Electric Cherry's numeric
|
||
match ID. `request_id` is unique for the server and at most 64 characters.
|
||
`amount` is a positive integer up to 1000000; withdrawal also accepts `"all"`.
|
||
The script resolves deposit `all` to its captured spendable points. `capacity`
|
||
is the current room below the game's bank withdrawal cap of 1000000 points.
|
||
An `all` withdrawal that exceeds this room is refused, rather than truncated.
|
||
|
||
Preparation requires the GUID to be present in the server's latest active match,
|
||
with the exact telemetry match key. Late joiners may need to wait for the next
|
||
30-second heartbeat. Unknown/destroyed servers, absent players and old matches
|
||
cannot prepare transfers. Settlement of an existing decision remains possible
|
||
after match completion, retention or server deletion.
|
||
|
||
Successful application responses include `ok`, `state`, `code`, `request_id`,
|
||
`amount` and `balance`. States are `balance`, `prepared`, `committed`, `cancelled`
|
||
or `rejected`. For balance reads, `amount` is the available balance. A business
|
||
rejection uses HTTP 200 with `ok: false`, `state: "rejected"` and a code such as
|
||
`insufficient_balance`, `empty`, `bank_limit`, `points_limit`, `not_in_match` or
|
||
`pending`. It never changes points. Authentication failures use 401/403,
|
||
malformed input 400/413/415, identity conflicts 409, and unavailable service 503.
|
||
|
||
Repeated requests return the saved result/state without applying a second
|
||
change. Settled IDs never reopen; a committed result retains its original
|
||
resulting balance even if later transfers change the account. Reusing an ID
|
||
with different transfer fields returns 409. A GUID has at most one prepared
|
||
transfer across all servers. SQLite transactions serialize checks, account
|
||
updates and ledger insertion, so concurrent requests cannot overspend funds or
|
||
deposit past the configured maximum.
|
||
|
||
## Lost responses and recovery
|
||
|
||
The game saves a cancellation decision **before** sending a prepare request.
|
||
Only a validated prepared reply lets it recheck the player's life, current
|
||
spendable points and withdrawal room. It then records a commit decision and
|
||
updates spendable points without yielding. The sender retries settlement with
|
||
the original identity, at delays of 1, 2, 4, 8, 16, then 30 seconds. Invalid,
|
||
truncated or uncorrelated replies never authorize points changes.
|
||
|
||
A failed/uncertain prepare leaves game points unchanged and queues cancellation.
|
||
Cancellation can arrive first: its tombstone blocks the delayed prepare. Death,
|
||
respawn, disconnect or match end before application also cancels. After a commit
|
||
decision, retries only finish the ledger; they never change game points again.
|
||
These points now belong to the current match, so an applied withdrawal is not
|
||
automatically refunded if that match ends or its server crashes.
|
||
|
||
Keep `fs_homepath/tws-bank/t5/` or `t6/` on persistent writable storage with a
|
||
single process per server/endpoint. Journals contain transfer data, not secrets.
|
||
On a map/process restart, saved decisions settle before new bank commands.
|
||
Unreadable storage disables the bank; failed writes prevent points changes.
|
||
Pending decisions remain until acknowledged, even on credential errors. Bank
|
||
holds do not expire automatically, because expiring a saved commit can lose a
|
||
deposit or create a free withdrawal. Restore connectivity/configuration and the
|
||
original journal to resolve holds; never delete a pending journal to unblock
|
||
the bank. If storage is permanently lost, reconcile against the retained
|
||
request and ledger records before any manual repair.
|
||
|
||
The journal provides recovery from map/process restart. It cannot make live
|
||
game memory and SQLite one atomic transaction or preserve match points after a
|
||
machine/storage failure. Back up persistent storage and verify crash behavior
|
||
in the actual Windows/Plutonium deployment before rollout.
|
||
|
||
## History and validation
|
||
|
||
Player profiles show the shared balance, pending status and 20 newest ledger
|
||
entries, including signed amounts, resulting balances, server, time and match.
|
||
Accounts, request IDs and ledger entries have no cascading match/server foreign
|
||
keys and are not deleted by match retention or server deletion. Retained IDs
|
||
also keep old retries idempotent. Expired match links become plain historical
|
||
IDs. Include these tables in normal database backups.
|
||
|
||
Run `npx vitest run tests/unit/bank.test.ts`, then the usual lint, type checks,
|
||
tests and build. TWS supplies `node tools/check_bank.mjs` and
|
||
`python3 tools/test_plugin_bank.py`. Live validation must cover both games:
|
||
deposit/withdraw amounts and `all`, insufficient funds and caps, joining before
|
||
telemetry, `.help`, score HUD updates, death/respawn/disconnect during requests,
|
||
lost prepare/commit replies, controller outage, disk failures and map/process
|
||
restart with pending work. Confirm earned score and leaderboard behavior, and
|
||
verify that another server's credential and the telemetry credential fail.
|