Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eda2c7e76d | ||
|
|
664e8da81b |
@@ -6,6 +6,48 @@ T5/T6 script and each native plugin. Current versions are recorded in
|
||||
|
||||
## 2026-10-12
|
||||
|
||||
### T6 newcomer join eligibility — `tws_t6_join.gsc` `1.0.1`
|
||||
|
||||
- Fix `t6/scripts/tws_t6_join.gsc` treating the initial T6 setup spawn as
|
||||
gameplay, which rejected a new spectator's `.join` on Farm Survival round 3.
|
||||
Wait for the stock delayed spectator transition, then record actual play.
|
||||
Continue observing later spawns so waiting for the next round cannot grant
|
||||
an extra life after death. Reject requests while a spawn is settling; a real
|
||||
death during that window also permanently closes eligibility.
|
||||
- T5 is unchanged: its separate SP spawn flow and `self.initialized` guard
|
||||
do not use T6's delayed game-mode spectator transition.
|
||||
- Compatibility: existing T6 common `1.0.0` or later; no new dependency
|
||||
requirement. Deploy the updated join script to `storage/t6/scripts/zm/`
|
||||
and restart the map. No full server-process restart is required.
|
||||
- Portable regression fixtures: `node tools/check_join.mjs`. Compilation and
|
||||
live Plutonium validation remain required; no deployment was performed.
|
||||
|
||||
### Persistent top-left Discord URL
|
||||
|
||||
| Script | Version | Affected source |
|
||||
| --- | --- | --- |
|
||||
| `tws_t5_hud.gsc` | `1.1.0` | `t5/scripts/tws_t5_hud.gsc` |
|
||||
| `tws_t5_common.gsc` | `1.1.0` | `t5/scripts/tws_t5_common.gsc` |
|
||||
| `tws_t6_hud.gsc` | `1.1.0` | `t6/scripts/tws_t6_hud.gsc` |
|
||||
| `tws_t6_common.gsc` | `1.1.0` | `t6/scripts/tws_t6_common.gsc` |
|
||||
|
||||
- Add `https://discord.thewrightserver.net` as a small, light-gray top-left
|
||||
HUD label, created once per connection, including spectator newcomers.
|
||||
It has no expiry or death/respawn reset and is cleaned up on disconnect.
|
||||
Move the gameplay stack down 20 HUD units to leave room above the timers;
|
||||
help and record positioning follow the shared layout automatically.
|
||||
- Both engines receive the feature using their existing HUD anchors: T5
|
||||
left/top safe-area alignment and T6 fullscreen alignment.
|
||||
- Compatibility: deploy each game's HUD and common `1.1.0` together for the
|
||||
reserved top line. No new native plugin or external dependency requirement.
|
||||
Other scripts and both plugins retain their existing versions.
|
||||
- Deployment: copy the updated HUD and common scripts to `storage/t5/scripts/sp/`
|
||||
or `storage/t6/scripts/zm/`, respectively, and restart the map. No full
|
||||
server-process restart is required. No deployment has been performed here.
|
||||
- Live validation remains required on both Plutonium games: check placement
|
||||
and readability, join as a spectator, die/respawn, reconnect, and open help
|
||||
and records to verify spacing and that only one URL label appears.
|
||||
|
||||
### Initial versioning baseline
|
||||
|
||||
Established independent version `1.0.0` entries for each of the 14 scripts and
|
||||
|
||||
+10
-1
@@ -11,7 +11,7 @@ Platform quirks and deploy commands: [NOTES.md](NOTES.md).
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| [tws_t5_common.gsc](tws_t5_common.gsc) | Shared config, HUD helpers, `.help`, connect/spawn registries, single `connected` listener + dispatcher |
|
||||
| [tws_t5_hud.gsc](tws_t5_hud.gsc) | Bottom-left HUD stack: timers, zombie count, health bar |
|
||||
| [tws_t5_hud.gsc](tws_t5_hud.gsc) | Top-left Discord URL, 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 |
|
||||
@@ -31,6 +31,15 @@ that directory:
|
||||
Do not leave both common scripts installed: duplicate connection listeners
|
||||
break T5's connect handshake. Existing dvar names are unchanged.
|
||||
|
||||
## Persistent Discord text
|
||||
|
||||
The HUD shows `https://discord.thewrightserver.net` in small light-gray text
|
||||
at the top-left, including for newcomers waiting in spectator mode. The label
|
||||
is created once per connection and persists across death and respawn. The
|
||||
gameplay HUD starts one line lower, keeping timers, help and records clear.
|
||||
Install HUD and common `1.1.0` together, then restart the map; no plugin update
|
||||
or full server-process restart is required.
|
||||
|
||||
## Map record announcements
|
||||
|
||||
Copy `tws_t5_leaderboard.gsc` and the updated `tws_t5_common.gsc` to
|
||||
|
||||
@@ -20,9 +20,9 @@ common_config()
|
||||
if (isDefined(level.hud_x))
|
||||
return;
|
||||
|
||||
// anchor: move the whole cluster with these two
|
||||
// Anchor the gameplay stack below the persistent Discord line.
|
||||
level.hud_x = 10;
|
||||
level.hud_y = 10;
|
||||
level.hud_y = 30;
|
||||
|
||||
level.hud_row_match = level.hud_y;
|
||||
level.hud_row_round = level.hud_y + 15;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* tws_t5_hud.gsc — bottom-left HUD stack. Timer / zombie count / health bar. */
|
||||
/* tws_t5_hud.gsc — top-left Discord URL, timers, zombie count and health bar. */
|
||||
|
||||
#include maps\_utility;
|
||||
#include common_scripts\utility;
|
||||
@@ -14,6 +14,21 @@ init()
|
||||
level thread game_over_watcher();
|
||||
|
||||
scripts\sp\tws_t5_common::register_spawn_handler(::hud_start);
|
||||
scripts\sp\tws_t5_common::register_connect_handler(::hud_discord);
|
||||
}
|
||||
|
||||
// One static label per connection, including newcomers waiting to spawn.
|
||||
hud_discord()
|
||||
{
|
||||
self endon("disconnect");
|
||||
|
||||
label = scripts\sp\tws_t5_common::make_text_elem(
|
||||
self, 10, 10, "left", level.hud_font_small, level.hud_c_label);
|
||||
label SetText("https://discord.thewrightserver.net");
|
||||
|
||||
elems = [];
|
||||
elems[elems.size] = label;
|
||||
self thread scripts\sp\tws_t5_common::destroy_on_disconnect(elems);
|
||||
}
|
||||
|
||||
hud_start()
|
||||
|
||||
@@ -118,6 +118,25 @@ at link time. The other four TWS modules can run without telemetry.
|
||||
|
||||
## Checks
|
||||
|
||||
### Newcomer eligibility
|
||||
|
||||
The stock [`_zm_gametype::onspawnplayer`](https://github.com/plutoniummod/t6-scripts/blob/2d8440321602f22f63f5335817110d60ddf223d5/ZM/Core/maps/mp/gametypes_zm/_zm_gametype.gsc)
|
||||
can schedule `spawnspectator` after 0.05 seconds while
|
||||
[`_globallogic_spawn::spawnplayer`](https://github.com/plutoniummod/t6-scripts/blob/2d8440321602f22f63f5335817110d60ddf223d5/ZM/Core/maps/mp/gametypes_zm/_globallogic_spawn.gsc)
|
||||
emits `spawned_player` at frame end. That event alone does not prove gameplay.
|
||||
Join `1.0.1` waits 0.1 seconds before checking session state and keeps listening
|
||||
after setup spectator spawns. `killed_player` also consumes eligibility so a
|
||||
death during that interval cannot restore newcomer access. The common dispatcher
|
||||
still starts each registered module once per connection.
|
||||
|
||||
Run `node tools/check_join.mjs` for portable lifecycle and request fixtures.
|
||||
These execute the join script with engine shims, not a GSC VM. Live validation:
|
||||
join Farm Survival during round 3 and remain spectating, use `.join` once, then
|
||||
try repeated requests, death, and subsequent rounds. On a fresh connection,
|
||||
wait for a normal round-start spawn, die, and verify `.join` is rejected.
|
||||
Also check a round-1 participant, reconnecting, disconnect during setup, and
|
||||
game over. Repeat on another T6 map; compile with the checker below first.
|
||||
|
||||
Local: `tools/check.ps1` compiles all seven files for T6 PC and checks shared
|
||||
references and entry points. It requires PowerShell and gsc-tool. The `.help`
|
||||
change passed the static entry-point/reference checks, but compilation and live
|
||||
|
||||
+10
-1
@@ -7,7 +7,7 @@ separate token config or database setup is required.
|
||||
| File | Role | Dependencies |
|
||||
| --- | --- | --- |
|
||||
| `tws_t6_common.gsc` | Shared HUD values, helpers, `.help`, connection and first-spawn dispatchers | Stock T6 |
|
||||
| `tws_t6_hud.gsc` | Match/round timers, name, zombie count and health bar | Common |
|
||||
| `tws_t6_hud.gsc` | Top-left Discord URL, match/round timers, name, zombie count and health bar | Common |
|
||||
| `tws_t6_afk.gsc` | `.afk` / `.back`, protection, movement and weapon blocking | Common |
|
||||
| `tws_t6_lock.gsc` | Lock at round 20; clear password on game over | Common, HUD |
|
||||
| `tws_t6_telemetry.gsc` | Async queued game reports (game_start/heartbeat/round_change/game_end) | tws-t6-gsc-utils |
|
||||
@@ -40,6 +40,15 @@ Keep the filenames and `scripts/zm` location so shared references resolve.
|
||||
To omit telemetry, remove its script; disabling it at runtime does not remove
|
||||
its plugin dependency during linking. The same rule applies to leaderboard.
|
||||
|
||||
## Persistent Discord text
|
||||
|
||||
The HUD shows `https://discord.thewrightserver.net` in small light-gray text
|
||||
at the top-left, including for newcomers waiting in spectator mode. The label
|
||||
is created once per connection and persists across death and respawn. The
|
||||
gameplay HUD starts one line lower, keeping timers, help and records clear.
|
||||
Install HUD and common `1.1.0` together, then restart the map; no plugin update
|
||||
or full server-process restart is required.
|
||||
|
||||
## Map record announcements
|
||||
|
||||
`tws_t6_leaderboard.gsc` reads the public Electric Cherry API without credentials.
|
||||
|
||||
@@ -22,9 +22,9 @@ common_config()
|
||||
if (isDefined(level.hud_x))
|
||||
return;
|
||||
|
||||
// Small inset from the physical screen corner (fullscreen HUD coordinates).
|
||||
// Fullscreen coordinates; reserve the top line for the Discord URL.
|
||||
level.hud_x = 10;
|
||||
level.hud_y = 10;
|
||||
level.hud_y = 30;
|
||||
|
||||
level.hud_row_match = level.hud_y;
|
||||
level.hud_row_round = level.hud_y + 15;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* tws_t6_hud.gsc - configurable HUD stack: timers, zombie count, health bar. */
|
||||
/* tws_t6_hud.gsc - top-left Discord URL, timers, zombie count and health bar. */
|
||||
|
||||
#include maps\mp\_utility;
|
||||
#include common_scripts\utility;
|
||||
@@ -16,6 +16,21 @@ init()
|
||||
level thread game_over_watcher();
|
||||
|
||||
scripts\zm\tws_t6_common::register_spawn_handler(::hud_start);
|
||||
scripts\zm\tws_t6_common::register_connect_handler(::hud_discord);
|
||||
}
|
||||
|
||||
// One static label per connection, including newcomers waiting to spawn.
|
||||
hud_discord()
|
||||
{
|
||||
self endon("disconnect");
|
||||
|
||||
label = scripts\zm\tws_t6_common::make_text_elem(
|
||||
self, 10, 10, "left", level.hud_font_small, level.hud_c_label);
|
||||
label SetText("https://discord.thewrightserver.net");
|
||||
|
||||
elems = [];
|
||||
elems[elems.size] = label;
|
||||
self thread scripts\zm\tws_t6_common::destroy_on_disconnect(elems);
|
||||
}
|
||||
|
||||
hud_start()
|
||||
|
||||
@@ -20,9 +20,44 @@ init()
|
||||
level thread join_chat_listener();
|
||||
}
|
||||
|
||||
// Never reset this on death or round change. Spectator spawns do not count.
|
||||
// T6 first emits spawned_player during setup, then moves late connectors
|
||||
// into spectator mode after 0.05 seconds. Observe the settled state instead.
|
||||
// The common spawn dispatcher runs only once, so keep listening ourselves.
|
||||
join_spawn()
|
||||
{
|
||||
self endon("disconnect");
|
||||
level endon("end_game");
|
||||
level endon("game_ended");
|
||||
level endon("intermission");
|
||||
|
||||
self thread join_track_death();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
self.tws_join_spawn_pending = true;
|
||||
wait 0.1;
|
||||
|
||||
if (isDefined(self.sessionstate) && self.sessionstate == "playing")
|
||||
self.tws_join_has_played = true;
|
||||
|
||||
self.tws_join_spawn_pending = false;
|
||||
if (is_true(self.tws_join_has_played))
|
||||
return;
|
||||
|
||||
self waittill("spawned_player");
|
||||
}
|
||||
}
|
||||
|
||||
// A real death during the settling window still consumes newcomer eligibility.
|
||||
// Never clear this flag on death, a round change, or a subsequent spawn.
|
||||
join_track_death()
|
||||
{
|
||||
self endon("disconnect");
|
||||
level endon("end_game");
|
||||
level endon("game_ended");
|
||||
level endon("intermission");
|
||||
|
||||
self waittill("killed_player");
|
||||
self.tws_join_has_played = true;
|
||||
}
|
||||
|
||||
@@ -51,8 +86,7 @@ join_request()
|
||||
level endon("game_ended");
|
||||
level endon("intermission");
|
||||
|
||||
// The spawn handler flag is set on first playable spawn only; spectator
|
||||
// spawns do not trigger it. Belt-and-suspenders against re-triggering.
|
||||
// Setup spawns do not consume eligibility; actual play and .join do.
|
||||
if (is_true(self.tws_join_has_played) || is_true(self.tws_join_used))
|
||||
{
|
||||
self iPrintLnBold("^3.join is only for newcomers who have not played yet");
|
||||
@@ -62,7 +96,8 @@ join_request()
|
||||
// Fail closed while the stock connection setup is still in progress.
|
||||
// T6 stock does not populate self.initialized the way T5 SP zombiemode
|
||||
// does; use sessionstate as the readiness signal.
|
||||
if (!isDefined(self.sessionstate) || self.sessionstate != "spectator")
|
||||
if (is_true(self.tws_join_spawn_pending) ||
|
||||
!isDefined(self.sessionstate) || self.sessionstate != "spectator")
|
||||
{
|
||||
self iPrintLnBold("^3.join is only available while waiting to enter the game");
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
// Execute the actual T6 join functions with a deterministic event/timer shim.
|
||||
// This checks script decisions, not GSC compilation or live engine behavior.
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import vm from 'node:vm';
|
||||
|
||||
const source = readFileSync(new URL('../t6/scripts/tws_t6_join.gsc', import.meta.url), 'utf8');
|
||||
const functions = [];
|
||||
for (const name of ['join_spawn', 'join_track_death', 'join_request']) {
|
||||
const match = new RegExp(`^${name}\\(\\)\\s*\\n\\{`, 'm').exec(source);
|
||||
// The original implementation has no death observer; allow regression runs.
|
||||
if (!match && name === 'join_track_death') continue;
|
||||
assert(match, `missing ${name}`);
|
||||
let end = match.index + match[0].length, depth = 1;
|
||||
const start = end;
|
||||
while (depth) {
|
||||
assert(end < source.length, `unclosed ${name}`);
|
||||
const ch = source[end++];
|
||||
if (ch === '{') depth++;
|
||||
if (ch === '}') depth--;
|
||||
}
|
||||
let body = source.slice(start, end - 1).replace(/\/\/[^\n]*/g, '');
|
||||
body = body
|
||||
.replace(/(self|level) endon\(([^;]+)\);/g, 'endon($1, $2);')
|
||||
.replace(/self thread (\w+)\(\);/g, 'startThread($1);')
|
||||
.replace(/self waittill\(([^;]+)\);/g, 'yield { entity: self, event: $1 };')
|
||||
.replace(/\bwait ([\d.]+);/g, 'yield { seconds: $1 };')
|
||||
.replace(/self iPrintLnBold\(/g, 'messages.push(')
|
||||
.replace(/self \[\[level.spawnplayer\]\]\(\);/g, 'spawnPlayer();')
|
||||
.replace(/maps\\mp\\zombies\\_zm::spectator_respawn/g, 'stockRespawn')
|
||||
.replace(/!join_has_active_player\(\)/g, '!activePlayer')
|
||||
.replace(/\.size\b/g, '.length');
|
||||
functions.push(`function* ${name}() { let points; ${body} }`);
|
||||
}
|
||||
|
||||
function engine() {
|
||||
let now = 0, current;
|
||||
const tasks = [], messages = [];
|
||||
const self = { sessionstate: 'playing', spectator_respawn: {} };
|
||||
const level = { round_number: 3, spawnplayer: true };
|
||||
let spawns = 0;
|
||||
function resume(task) {
|
||||
const previous = current;
|
||||
current = task;
|
||||
const result = task.generator.next();
|
||||
task.done = result.done;
|
||||
task.wait = result.value;
|
||||
if (task.wait?.seconds !== undefined) task.at = now + task.wait.seconds;
|
||||
else task.at = Infinity;
|
||||
current = previous;
|
||||
}
|
||||
function startThread(fn) {
|
||||
const task = { generator: fn(), ends: [] };
|
||||
tasks.push(task);
|
||||
resume(task);
|
||||
}
|
||||
function event(entity, event) {
|
||||
const listeners = tasks.filter((task) => !task.done);
|
||||
for (const task of listeners) {
|
||||
if (task.ends.some(([e, name]) => e === entity && name === event)) task.done = true;
|
||||
}
|
||||
for (const task of listeners) {
|
||||
if (!task.done && task.wait?.entity === entity && task.wait.event === event) resume(task);
|
||||
}
|
||||
}
|
||||
function advance(seconds) {
|
||||
const until = now + seconds;
|
||||
for (;;) {
|
||||
const next = tasks.filter((task) => !task.done && task.at <= until)
|
||||
.sort((a, b) => a.at - b.at)[0];
|
||||
if (!next) break;
|
||||
now = next.at;
|
||||
resume(next);
|
||||
}
|
||||
now = until;
|
||||
}
|
||||
const c = vm.createContext({
|
||||
self, level, messages, activePlayer: true, stockRespawn: () => {},
|
||||
isDefined: (value) => value !== undefined && value !== null,
|
||||
is_true: (value) => value === true || value === 1,
|
||||
endon: (entity, event) => current.ends.push([entity, event]),
|
||||
startThread,
|
||||
getstructarray: () => [{}], randomInt: () => 0,
|
||||
spawnPlayer: () => {
|
||||
spawns++;
|
||||
self.sessionstate = 'playing';
|
||||
event(self, 'spawned_player');
|
||||
},
|
||||
});
|
||||
vm.runInContext(functions.join('\n'), c);
|
||||
return { self, level, c, messages, tasks, advance,
|
||||
start: () => startThread(c.join_spawn),
|
||||
request: () => startThread(c.join_request),
|
||||
event: (name) => event(self, name),
|
||||
end: (name) => event(level, name),
|
||||
get spawns() { return spawns; },
|
||||
};
|
||||
}
|
||||
|
||||
let checks = 0;
|
||||
function check(name, run) {
|
||||
try { run(); checks++; }
|
||||
catch (error) { throw new Error(name, { cause: error }); }
|
||||
}
|
||||
function newcomer() {
|
||||
const e = engine();
|
||||
e.start(); // Common's once-per-connection callback, at spawned_player.
|
||||
e.advance(0.05);
|
||||
e.self.sessionstate = 'spectator'; // Stock delayed setup transition.
|
||||
e.event('spawned_spectator');
|
||||
e.advance(0.1);
|
||||
return e;
|
||||
}
|
||||
|
||||
check('Farm round 3 setup spawn leaves a newcomer eligible for exactly one join', () => {
|
||||
const e = newcomer();
|
||||
assert(!e.self.tws_join_has_played);
|
||||
e.request();
|
||||
assert.equal(e.spawns, 1);
|
||||
assert.equal(e.self.tws_join_used, true);
|
||||
e.request();
|
||||
e.self.sessionstate = 'spectator';
|
||||
e.event('killed_player');
|
||||
e.level.round_number++;
|
||||
e.request();
|
||||
assert.equal(e.spawns, 1);
|
||||
assert.match(e.messages.at(-1), /only for newcomers/);
|
||||
});
|
||||
check('setup transition blocks requests until settled', () => {
|
||||
const e = engine();
|
||||
e.start();
|
||||
e.advance(0.05);
|
||||
e.self.sessionstate = 'spectator';
|
||||
e.request();
|
||||
assert.equal(e.spawns, 0);
|
||||
assert.match(e.messages.at(-1), /waiting to enter/);
|
||||
e.advance(0.1);
|
||||
e.request();
|
||||
assert.equal(e.spawns, 1);
|
||||
});
|
||||
check('a normal round-start spawn closes eligibility even after later death', () => {
|
||||
const e = newcomer();
|
||||
e.level.round_number++;
|
||||
e.self.sessionstate = 'playing';
|
||||
e.event('spawned_player');
|
||||
e.advance(0.11);
|
||||
assert.equal(e.self.tws_join_has_played, true);
|
||||
e.self.sessionstate = 'spectator';
|
||||
e.request();
|
||||
assert.equal(e.spawns, 0);
|
||||
assert.match(e.messages.at(-1), /only for newcomers/);
|
||||
});
|
||||
check('round-1 participants cannot join again after death', () => {
|
||||
const e = engine();
|
||||
e.level.round_number = 1;
|
||||
e.start();
|
||||
e.advance(0.11);
|
||||
e.self.sessionstate = 'spectator';
|
||||
e.request();
|
||||
assert.equal(e.spawns, 0);
|
||||
assert.equal(e.self.tws_join_has_played, true);
|
||||
});
|
||||
for (const initial of [true, false]) {
|
||||
check(`death during ${initial ? 'initial' : 'later'} spawn settling consumes eligibility`, () => {
|
||||
const e = initial ? engine() : newcomer();
|
||||
if (initial) e.start();
|
||||
else {
|
||||
e.self.sessionstate = 'playing';
|
||||
e.event('spawned_player');
|
||||
}
|
||||
e.advance(0.02);
|
||||
e.event('killed_player');
|
||||
e.self.sessionstate = 'spectator';
|
||||
e.advance(0.11);
|
||||
e.request();
|
||||
assert.equal(e.self.tws_join_has_played, true);
|
||||
assert.equal(e.spawns, 0);
|
||||
});
|
||||
}
|
||||
check('repeated spectator setup events keep listening for an actual spawn', () => {
|
||||
const e = newcomer();
|
||||
e.event('spawned_player');
|
||||
e.advance(0.11);
|
||||
assert(!e.self.tws_join_has_played);
|
||||
e.self.sessionstate = 'playing';
|
||||
e.event('spawned_player');
|
||||
e.advance(0.11);
|
||||
assert.equal(e.self.tws_join_has_played, true);
|
||||
});
|
||||
for (const name of ['disconnect', 'end_game', 'game_ended', 'intermission']) {
|
||||
check(`${name} stops observers during setup and while waiting`, () => {
|
||||
for (const setup of [true, false]) {
|
||||
const e = setup ? engine() : newcomer();
|
||||
if (setup) e.start();
|
||||
if (name === 'disconnect') e.event(name);
|
||||
else e.end(name);
|
||||
e.advance(1);
|
||||
e.event('killed_player');
|
||||
assert(!e.self.tws_join_has_played);
|
||||
assert(e.tasks.every((task) => task.done));
|
||||
}
|
||||
});
|
||||
}
|
||||
for (const [name, set] of [
|
||||
['intermission', (e) => { e.level.intermission = true; }],
|
||||
['game ended', (e) => { e.level.gameended = true; }],
|
||||
['no active player', (e) => { e.c.activePlayer = false; }],
|
||||
['missing round', (e) => { delete e.level.round_number; }],
|
||||
['missing spawn callback', (e) => { delete e.level.spawnplayer; }],
|
||||
]) {
|
||||
check(`${name} still rejects joining`, () => {
|
||||
const e = newcomer();
|
||||
set(e);
|
||||
e.request();
|
||||
assert.equal(e.spawns, 0);
|
||||
assert.match(e.messages.at(-1), /not ready/);
|
||||
});
|
||||
}
|
||||
check('reconnecting starts independent eligibility', () => {
|
||||
const first = newcomer();
|
||||
first.request();
|
||||
first.event('disconnect');
|
||||
const second = newcomer();
|
||||
second.request();
|
||||
assert.equal(second.spawns, 1);
|
||||
});
|
||||
console.log(`PASS ${checks} T6 join lifecycle/request checks (engine shims; live validation required)`);
|
||||
+5
-5
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"tws_t5_afk.gsc": "1.0.0",
|
||||
"tws_t5_common.gsc": "1.0.0",
|
||||
"tws_t5_hud.gsc": "1.0.0",
|
||||
"tws_t5_common.gsc": "1.1.0",
|
||||
"tws_t5_hud.gsc": "1.1.0",
|
||||
"tws_t5_join.gsc": "1.0.0",
|
||||
"tws_t5_leaderboard.gsc": "1.0.0",
|
||||
"tws_t5_lock.gsc": "1.0.0",
|
||||
"tws_t5_telemetry.gsc": "1.0.0",
|
||||
"tws_t6_afk.gsc": "1.0.0",
|
||||
"tws_t6_common.gsc": "1.0.0",
|
||||
"tws_t6_hud.gsc": "1.0.0",
|
||||
"tws_t6_join.gsc": "1.0.0",
|
||||
"tws_t6_common.gsc": "1.1.0",
|
||||
"tws_t6_hud.gsc": "1.1.0",
|
||||
"tws_t6_join.gsc": "1.0.1",
|
||||
"tws_t6_leaderboard.gsc": "1.0.0",
|
||||
"tws_t6_lock.gsc": "1.0.0",
|
||||
"tws_t6_telemetry.gsc": "1.0.0",
|
||||
|
||||
Reference in New Issue
Block a user