@genex-ai/multiplayer 0.10.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/dist/index.d.ts +17 -12
- package/dist/index.js +72 -18
- package/dist/schema.d.ts +8 -0
- package/dist/schema.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,14 +22,15 @@ const room = await connect<State>({
|
|
|
22
22
|
urls: getColyseusUrls(), // regional relays for this session — SDK joins the fastest
|
|
23
23
|
room: '<game-id>', // everyone with the same id shares a room (use the project slug)
|
|
24
24
|
name: 'ada', // optional display name
|
|
25
|
-
auth: getColyseusAuth()
|
|
25
|
+
auth: () => getColyseusAuth(), // REQUIRED — resolved fresh for this connect
|
|
26
26
|
});
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
`auth` is **required** — the relay rejects tokenless joins. Get it from `@genex-ai/embed-sdk`:
|
|
30
30
|
`await waitForPlayer()` first (never `waitForAuth()` — that resolves only for signed-in accounts
|
|
31
|
-
and keeps guests out forever), then
|
|
32
|
-
tokens rotate every ~10 min, so never cache the object
|
|
31
|
+
and keeps guests out forever), then pass `() => getColyseusAuth()` so each explicit
|
|
32
|
+
`connect()` reads it fresh — tokens rotate every ~10 min, so never cache the object
|
|
33
|
+
across reconnects. Never log it.
|
|
33
34
|
|
|
34
35
|
Different `room` ids are **fully isolated** — players in game A never see game B.
|
|
35
36
|
|
|
@@ -46,7 +47,7 @@ room.me.snap({ x: spawnX, y: spawnY, z: spawnZ }); // respawn/teleport: hard-res
|
|
|
46
47
|
|
|
47
48
|
## `players` — everyone in the room
|
|
48
49
|
|
|
49
|
-
A fresh `Map` each read; **includes you** (skip with `id === room.id`). For remote players `p.state` is **auto-smoothed** (interpolated); for yourself it's live. `p.stateRaw` is always the latest raw snapshot (use it for hit-tests / logic).
|
|
50
|
+
A fresh `Map` each read; **includes you** (skip with `id === room.id`). For remote players `p.state` is **auto-smoothed** (interpolated); for yourself it's live. `p.stateRaw` is always the latest raw snapshot (use it for hit-tests / logic). A reserved reconnecting seat stays in `players` with `p.connected === false`; use `room.activePlayers` when gameplay needs the live transport quorum.
|
|
50
51
|
|
|
51
52
|
```ts
|
|
52
53
|
for (const [id, p] of room.players) {
|
|
@@ -253,7 +254,7 @@ await waitForPlayer(); // identity gate (guest OR signed-in) — see Connect a
|
|
|
253
254
|
const room = await connect<S>({
|
|
254
255
|
urls: getColyseusUrls(),
|
|
255
256
|
room: GAME_ID,
|
|
256
|
-
auth: getColyseusAuth()
|
|
257
|
+
auth: () => getColyseusAuth(), // REQUIRED — resolved fresh for this connect
|
|
257
258
|
});
|
|
258
259
|
|
|
259
260
|
const me = { x: 0, z: 0 };
|
|
@@ -317,7 +318,7 @@ A failed queue join is **retried automatically** with backoff (~10s total), so t
|
|
|
317
318
|
(a token mid-rotation, identity still settling on page load) never surface. Only a persistent
|
|
318
319
|
failure emits `error`; the handle then idles until you call `mm.retry()`.
|
|
319
320
|
|
|
320
|
-
**`mm.matchmaking`** (read every frame for your HUD): `status` (`searching` | `waiting` | `countdown` | `playing` | `ended`; the legacy `spectating` is never emitted — server-side spectators were removed), `queue` (`{ position, size }`), `players
|
|
321
|
+
**`mm.matchmaking`** (read every frame for your HUD): `status` (`searching` | `waiting` | `countdown` | `playing` | `ended`; the legacy `spectating` is never emitted — server-side spectators were removed), `queue` (`{ position, size }`), `players` (reserved seats), `connectedPlayers` (live transports), `opponents`, `teams`, `myTeam`, `scores`, `winCondition`, `config` (numeric knobs), `roundId`, `winnerId`, `message`. `championId`/`challengerId` are 1v1 aliases. During an automatic reconnect, the SDK retains the last coherent roster and match view until the replacement room hydrates, so status does not transiently regress to `waiting`.
|
|
321
322
|
|
|
322
323
|
Configure once in the game's `package.json` (reported at `genex preview` AND `genex publish`;
|
|
323
324
|
removing it clears the stored config on the next preview/publish — never sent from the client):
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export { PlayerState, RoomState } from './schema.js';
|
|
2
2
|
import '@colyseus/schema';
|
|
3
3
|
|
|
4
|
+
type ColyseusAuth = {
|
|
5
|
+
embedToken: string;
|
|
6
|
+
};
|
|
7
|
+
type AuthSource = ColyseusAuth | (() => ColyseusAuth | undefined);
|
|
4
8
|
type ConnectOptions = {
|
|
5
9
|
/** Regional relay urls (pass `getColyseusUrls()`) — the SDK pings each and connects to the
|
|
6
10
|
* lowest-latency one; a single entry just connects. */
|
|
@@ -17,18 +21,19 @@ type ConnectOptions = {
|
|
|
17
21
|
* `genexEmbed.getColyseusAuth()` AFTER `await genexEmbed.waitForPlayer()`
|
|
18
22
|
* (from `@genex-ai/embed-sdk`). NOT `waitForAuth()` — that resolves only for
|
|
19
23
|
* signed-in accounts and would keep guests out of multiplayer forever.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
24
|
+
* Prefer a supplier `() => getColyseusAuth()` so every explicit connect()
|
|
25
|
+
* call reads the current token (tokens rotate every ~10 min). A current
|
|
26
|
+
* value remains supported for backward compatibility. NEVER log it.
|
|
22
27
|
*/
|
|
23
|
-
auth?:
|
|
24
|
-
embedToken: string;
|
|
25
|
-
};
|
|
28
|
+
auth?: AuthSource;
|
|
26
29
|
/** Extra join options passed to the room. */
|
|
27
30
|
options?: Record<string, unknown>;
|
|
28
31
|
};
|
|
29
32
|
type PlayerView<TState> = {
|
|
30
33
|
id: string;
|
|
31
34
|
name: string;
|
|
35
|
+
/** Transport is live now. False while this reserved seat is inside reconnect grace. */
|
|
36
|
+
connected: boolean;
|
|
32
37
|
/** State to render — for remote players this is **auto-smoothed** (interpolated);
|
|
33
38
|
* for yourself it's live. Numeric fields are lerped; everything else snaps. */
|
|
34
39
|
state: TState;
|
|
@@ -81,6 +86,8 @@ type Session<TState extends Record<string, unknown>> = {
|
|
|
81
86
|
* `state` is auto-smoothed; read `stateRaw` for the raw latest value.
|
|
82
87
|
*/
|
|
83
88
|
readonly players: Map<string, PlayerView<TState>>;
|
|
89
|
+
/** Connected subset of `players`; excludes seats currently held only by reconnect grace. */
|
|
90
|
+
readonly activePlayers: Map<string, PlayerView<TState>>;
|
|
84
91
|
readonly me: {
|
|
85
92
|
/**
|
|
86
93
|
* Publish your synced state (replaces it wholesale). Call on a **fixed tick**
|
|
@@ -198,7 +205,7 @@ type Session<TState extends Record<string, unknown>> = {
|
|
|
198
205
|
* await waitForPlayer(); // identity gate (guest OR signed-in)
|
|
199
206
|
* const room = await connect<{ x: number; y: number; z: number }>({
|
|
200
207
|
* url: 'ws://localhost:2567', room: gameId, name: 'ada',
|
|
201
|
-
* auth: getColyseusAuth()
|
|
208
|
+
* auth: () => getColyseusAuth(), // REQUIRED — fresh on every explicit connect call
|
|
202
209
|
* });
|
|
203
210
|
* room.me.set({ x, y, z }); // typed + validated by the compiler
|
|
204
211
|
* room.players.get(id)?.state.x; // number — auto-smoothed for remote players
|
|
@@ -220,6 +227,8 @@ type MatchmakingView = {
|
|
|
220
227
|
};
|
|
221
228
|
/** Every active participant id (N-player: 1v1, FFA, teams). */
|
|
222
229
|
players: string[];
|
|
230
|
+
/** Participants whose transport is live now; graced seats remain only in `players`. */
|
|
231
|
+
connectedPlayers: string[];
|
|
223
232
|
/** The other active player id(s) — everyone in `players` except you. */
|
|
224
233
|
opponents: string[];
|
|
225
234
|
/** teamId -> member ids for team presets; empty for FFA + 1v1. */
|
|
@@ -276,11 +285,7 @@ type MatchmakeOptions = {
|
|
|
276
285
|
* requeue). Pass a FUNCTION — `() => genexEmbed.getColyseusAuth()` — so each (re)join reads a FRESH
|
|
277
286
|
* token; a static object is read only once and will be rejected once it expires.
|
|
278
287
|
*/
|
|
279
|
-
auth?:
|
|
280
|
-
embedToken: string;
|
|
281
|
-
} | (() => {
|
|
282
|
-
embedToken: string;
|
|
283
|
-
} | undefined);
|
|
288
|
+
auth?: AuthSource;
|
|
284
289
|
};
|
|
285
290
|
/**
|
|
286
291
|
* Join a game's matchmaking. Returns immediately in the SEARCHING state (`session` is null); the
|
|
@@ -311,4 +316,4 @@ declare function createPrivate<TState extends Record<string, unknown> = never>(o
|
|
|
311
316
|
*/
|
|
312
317
|
declare function joinPrivate<TState extends Record<string, unknown> = never>(code: string, opts: MatchmakeOptions): Promise<Matchmaking<TState>>;
|
|
313
318
|
|
|
314
|
-
export { type ConnectOptions, type MatchStatus, type MatchmakeOptions, type Matchmaking, type MatchmakingView, type ObjectControlOperation, type ObjectControlOptions, type ObjectControlReason, type ObjectControlResult, type ObjectView, type PlayerView, type PrivateMatch, type Session, connect, createPrivate, joinPrivate, matchmake };
|
|
319
|
+
export { type AuthSource, type ColyseusAuth, type ConnectOptions, type MatchStatus, type MatchmakeOptions, type Matchmaking, type MatchmakingView, type ObjectControlOperation, type ObjectControlOptions, type ObjectControlReason, type ObjectControlResult, type ObjectView, type PlayerView, type PrivateMatch, type Session, connect, createPrivate, joinPrivate, matchmake };
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ async function relayClient(opts) {
|
|
|
33
33
|
if (!url) throw new Error("genex multiplayer: `urls` must contain at least one relay URL");
|
|
34
34
|
return new Client(url);
|
|
35
35
|
}
|
|
36
|
+
var resolveAuth = (auth) => typeof auth === "function" ? auth() : auth;
|
|
36
37
|
function parseData(s) {
|
|
37
38
|
if (!s) return { state: {} };
|
|
38
39
|
try {
|
|
@@ -161,7 +162,7 @@ async function connect(opts) {
|
|
|
161
162
|
const room = await client.joinOrCreate(RELAY_ROOM_TYPE, {
|
|
162
163
|
gameId: opts.room,
|
|
163
164
|
name: opts.name,
|
|
164
|
-
embedToken: opts.auth?.embedToken,
|
|
165
|
+
embedToken: resolveAuth(opts.auth)?.embedToken,
|
|
165
166
|
...opts.options,
|
|
166
167
|
__v: MP_PROTOCOL
|
|
167
168
|
// tells the relay this client hides reserved `__` keys (see MP_PROTOCOL)
|
|
@@ -368,6 +369,7 @@ function attachSession(client, initialRoom) {
|
|
|
368
369
|
};
|
|
369
370
|
const parseCache = /* @__PURE__ */ new Map();
|
|
370
371
|
const playerEpochs = /* @__PURE__ */ new Map();
|
|
372
|
+
let lastPlayersView = /* @__PURE__ */ new Map();
|
|
371
373
|
const cachedParse = (id, raw) => {
|
|
372
374
|
if (!raw) return {};
|
|
373
375
|
const hit = parseCache.get(id);
|
|
@@ -658,7 +660,6 @@ function attachSession(client, initialRoom) {
|
|
|
658
660
|
try {
|
|
659
661
|
const next = await client.reconnect(token);
|
|
660
662
|
room = next;
|
|
661
|
-
connected = true;
|
|
662
663
|
const reconnectGap = reconnectStartedAt ? nowMs() - reconnectStartedAt : 0;
|
|
663
664
|
reconnectStartedAt = 0;
|
|
664
665
|
if (reconnectGap > 1e3) {
|
|
@@ -667,6 +668,7 @@ function attachSession(client, initialRoom) {
|
|
|
667
668
|
const hydrated = wireRoom(next);
|
|
668
669
|
await Promise.race([hydrated, new Promise((r) => setTimeout(r, 2e3))]);
|
|
669
670
|
if (room !== next || dead || intentionalLeave) return;
|
|
671
|
+
connected = true;
|
|
670
672
|
syncHostTicks();
|
|
671
673
|
for (const pending of pendingControls.values()) sendPendingControl(pending);
|
|
672
674
|
emit("reconnected");
|
|
@@ -718,10 +720,22 @@ function attachSession(client, initialRoom) {
|
|
|
718
720
|
} else {
|
|
719
721
|
state = sampleSmoothed(id, localNow) ?? latest;
|
|
720
722
|
}
|
|
721
|
-
out.set(id, {
|
|
723
|
+
out.set(id, {
|
|
724
|
+
id,
|
|
725
|
+
name: p.name,
|
|
726
|
+
connected: p.connected !== false,
|
|
727
|
+
// old relay schemas did not carry the field
|
|
728
|
+
state,
|
|
729
|
+
stateRaw: raw
|
|
730
|
+
});
|
|
722
731
|
});
|
|
732
|
+
if (!connected && out.size === 0 && lastPlayersView.size > 0) return new Map(lastPlayersView);
|
|
733
|
+
lastPlayersView = out;
|
|
723
734
|
return out;
|
|
724
735
|
},
|
|
736
|
+
get activePlayers() {
|
|
737
|
+
return new Map([...session.players].filter(([, player]) => player.connected));
|
|
738
|
+
},
|
|
725
739
|
me: {
|
|
726
740
|
set(state) {
|
|
727
741
|
if (SEND_TIME_KEY in state) {
|
|
@@ -894,7 +908,6 @@ function attachSession(client, initialRoom) {
|
|
|
894
908
|
return session;
|
|
895
909
|
}
|
|
896
910
|
var SEARCH_BACKOFF_MS = [1e3, 2e3, 4e3];
|
|
897
|
-
var resolveAuth = (auth) => typeof auth === "function" ? auth() : auth;
|
|
898
911
|
var matchJoinOptions = (opts) => ({
|
|
899
912
|
gameId: opts.room,
|
|
900
913
|
name: opts.name,
|
|
@@ -911,6 +924,7 @@ function buildView(matchRoom, queueInfo) {
|
|
|
911
924
|
role: "player",
|
|
912
925
|
queue: queueInfo,
|
|
913
926
|
players: [],
|
|
927
|
+
connectedPlayers: [],
|
|
914
928
|
opponents: [],
|
|
915
929
|
teams: {},
|
|
916
930
|
myTeam: null,
|
|
@@ -929,6 +943,7 @@ function buildView(matchRoom, queueInfo) {
|
|
|
929
943
|
const challengerId = m.challengerId ?? null;
|
|
930
944
|
const teams = m.teams ?? {};
|
|
931
945
|
const players = m.players ?? [championId, challengerId].filter((x) => !!x);
|
|
946
|
+
const connectedPlayers = m.connectedPlayers ?? players;
|
|
932
947
|
const isPlayer = myId != null && players.includes(myId);
|
|
933
948
|
const status = isPlayer ? m.phase : "spectating";
|
|
934
949
|
const opponents = players.filter((id) => id !== myId);
|
|
@@ -939,6 +954,7 @@ function buildView(matchRoom, queueInfo) {
|
|
|
939
954
|
role: isPlayer ? "player" : "spectator",
|
|
940
955
|
queue: { position: 0, size: 0 },
|
|
941
956
|
players,
|
|
957
|
+
connectedPlayers,
|
|
942
958
|
opponents,
|
|
943
959
|
teams,
|
|
944
960
|
myTeam,
|
|
@@ -968,9 +984,13 @@ async function matchmake(opts) {
|
|
|
968
984
|
let queueInfo = { position: 0, size: 0 };
|
|
969
985
|
let cancelled = false;
|
|
970
986
|
let searching = false;
|
|
987
|
+
let lifecycleEpoch = 0;
|
|
988
|
+
let seating = false;
|
|
989
|
+
let requeueing = false;
|
|
971
990
|
const search = async () => {
|
|
972
991
|
if (cancelled || searching) return;
|
|
973
992
|
searching = true;
|
|
993
|
+
const epoch = ++lifecycleEpoch;
|
|
974
994
|
try {
|
|
975
995
|
session = null;
|
|
976
996
|
matchRoom = null;
|
|
@@ -994,7 +1014,7 @@ async function matchmake(opts) {
|
|
|
994
1014
|
if (cancelled) return;
|
|
995
1015
|
continue;
|
|
996
1016
|
}
|
|
997
|
-
if (cancelled) {
|
|
1017
|
+
if (cancelled || epoch !== lifecycleEpoch) {
|
|
998
1018
|
void q.leave();
|
|
999
1019
|
return;
|
|
1000
1020
|
}
|
|
@@ -1003,10 +1023,13 @@ async function matchmake(opts) {
|
|
|
1003
1023
|
queueInfo = { position: msg.position, size: msg.size };
|
|
1004
1024
|
emit("queue", msg);
|
|
1005
1025
|
});
|
|
1006
|
-
q.onMessage(
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1026
|
+
q.onMessage("seat", (msg) => {
|
|
1027
|
+
if (cancelled || epoch !== lifecycleEpoch || queueRoom !== q || seating) return;
|
|
1028
|
+
seating = true;
|
|
1029
|
+
queueRoom = null;
|
|
1030
|
+
void q.leave();
|
|
1031
|
+
void consumeSeat(msg.reservation, msg.role, epoch);
|
|
1032
|
+
});
|
|
1010
1033
|
q.onLeave(() => {
|
|
1011
1034
|
if (cancelled || queueRoom !== q) return;
|
|
1012
1035
|
queueRoom = null;
|
|
@@ -1026,10 +1049,10 @@ async function matchmake(opts) {
|
|
|
1026
1049
|
searching = false;
|
|
1027
1050
|
}
|
|
1028
1051
|
};
|
|
1029
|
-
const consumeSeat = async (reservation, role) => {
|
|
1052
|
+
const consumeSeat = async (reservation, role, epoch) => {
|
|
1030
1053
|
try {
|
|
1031
1054
|
const mr = await client.consumeSeatReservation(reservation);
|
|
1032
|
-
if (cancelled) {
|
|
1055
|
+
if (cancelled || epoch !== lifecycleEpoch) {
|
|
1033
1056
|
void mr.leave();
|
|
1034
1057
|
return;
|
|
1035
1058
|
}
|
|
@@ -1038,7 +1061,9 @@ async function matchmake(opts) {
|
|
|
1038
1061
|
session = s;
|
|
1039
1062
|
s.on("match:start", (m) => emit("matchStart", m));
|
|
1040
1063
|
s.on("match:ended", (m) => emit("matchEnded", m));
|
|
1041
|
-
s.on("match:requeue", () =>
|
|
1064
|
+
s.on("match:requeue", () => {
|
|
1065
|
+
if (session === s) void requeue();
|
|
1066
|
+
});
|
|
1042
1067
|
s.on("disconnect", () => {
|
|
1043
1068
|
if (!cancelled && session === s) void requeue();
|
|
1044
1069
|
});
|
|
@@ -1049,23 +1074,50 @@ async function matchmake(opts) {
|
|
|
1049
1074
|
emit("matched");
|
|
1050
1075
|
if (role === "spectator") emit("spectate");
|
|
1051
1076
|
} catch {
|
|
1052
|
-
|
|
1077
|
+
if (!cancelled && epoch === lifecycleEpoch) {
|
|
1078
|
+
seating = false;
|
|
1079
|
+
void search();
|
|
1080
|
+
}
|
|
1081
|
+
} finally {
|
|
1082
|
+
if (epoch === lifecycleEpoch) seating = false;
|
|
1053
1083
|
}
|
|
1054
1084
|
};
|
|
1055
1085
|
const requeue = async () => {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1086
|
+
if (cancelled || requeueing) return;
|
|
1087
|
+
requeueing = true;
|
|
1088
|
+
try {
|
|
1089
|
+
lifecycleEpoch++;
|
|
1090
|
+
seating = false;
|
|
1091
|
+
session?.leave();
|
|
1092
|
+
matchRoom = null;
|
|
1093
|
+
session = null;
|
|
1094
|
+
await search();
|
|
1095
|
+
} finally {
|
|
1096
|
+
requeueing = false;
|
|
1097
|
+
}
|
|
1060
1098
|
};
|
|
1061
1099
|
void search();
|
|
1062
1100
|
const liveRoom = () => session ? roomOf(session) : null;
|
|
1101
|
+
let viewSession = null;
|
|
1102
|
+
let lastHydratedView = null;
|
|
1103
|
+
const stableView = () => {
|
|
1104
|
+
if (session !== viewSession) {
|
|
1105
|
+
viewSession = session;
|
|
1106
|
+
lastHydratedView = null;
|
|
1107
|
+
}
|
|
1108
|
+
const live = liveRoom();
|
|
1109
|
+
const hydrated = live?.state?.shared?.get(MATCH_KEY) != null;
|
|
1110
|
+
if (!hydrated && session && lastHydratedView) return lastHydratedView;
|
|
1111
|
+
const next = buildView(live, queueInfo);
|
|
1112
|
+
if (hydrated) lastHydratedView = next;
|
|
1113
|
+
return next;
|
|
1114
|
+
};
|
|
1063
1115
|
return {
|
|
1064
1116
|
get session() {
|
|
1065
1117
|
return session;
|
|
1066
1118
|
},
|
|
1067
1119
|
get matchmaking() {
|
|
1068
|
-
return
|
|
1120
|
+
return stableView();
|
|
1069
1121
|
},
|
|
1070
1122
|
on,
|
|
1071
1123
|
eliminated() {
|
|
@@ -1083,6 +1135,8 @@ async function matchmake(opts) {
|
|
|
1083
1135
|
},
|
|
1084
1136
|
cancel() {
|
|
1085
1137
|
cancelled = true;
|
|
1138
|
+
lifecycleEpoch++;
|
|
1139
|
+
seating = false;
|
|
1086
1140
|
session?.leave();
|
|
1087
1141
|
if (!session && matchRoom) void matchRoom.leave();
|
|
1088
1142
|
if (queueRoom) void queueRoom.leave();
|
package/dist/schema.d.ts
CHANGED
|
@@ -5,10 +5,12 @@ declare const PlayerState: _colyseus_schema.SchemaWithExtendsConstructor<{
|
|
|
5
5
|
name: "string";
|
|
6
6
|
data: "string";
|
|
7
7
|
epoch: "number";
|
|
8
|
+
connected: "boolean";
|
|
8
9
|
}, _colyseus_schema.AssignableProps<_colyseus_schema.InferSchemaInstanceType<{
|
|
9
10
|
name: "string";
|
|
10
11
|
data: "string";
|
|
11
12
|
epoch: "number";
|
|
13
|
+
connected: "boolean";
|
|
12
14
|
}>>, typeof _colyseus_schema.Schema>;
|
|
13
15
|
type PlayerState = SchemaType<typeof PlayerState>;
|
|
14
16
|
declare const RoomState: _colyseus_schema.SchemaWithExtendsConstructor<{
|
|
@@ -17,15 +19,18 @@ declare const RoomState: _colyseus_schema.SchemaWithExtendsConstructor<{
|
|
|
17
19
|
name: "string";
|
|
18
20
|
data: "string";
|
|
19
21
|
epoch: "number";
|
|
22
|
+
connected: "boolean";
|
|
20
23
|
}, _colyseus_schema.AssignableProps<_colyseus_schema.InferSchemaInstanceType<{
|
|
21
24
|
name: "string";
|
|
22
25
|
data: "string";
|
|
23
26
|
epoch: "number";
|
|
27
|
+
connected: "boolean";
|
|
24
28
|
}>>, typeof _colyseus_schema.Schema>;
|
|
25
29
|
default: MapSchema<_colyseus_schema.InferSchemaInstanceType<{
|
|
26
30
|
name: "string";
|
|
27
31
|
data: "string";
|
|
28
32
|
epoch: "number";
|
|
33
|
+
connected: "boolean";
|
|
29
34
|
}>, string>;
|
|
30
35
|
};
|
|
31
36
|
shared: {
|
|
@@ -38,15 +43,18 @@ declare const RoomState: _colyseus_schema.SchemaWithExtendsConstructor<{
|
|
|
38
43
|
name: "string";
|
|
39
44
|
data: "string";
|
|
40
45
|
epoch: "number";
|
|
46
|
+
connected: "boolean";
|
|
41
47
|
}, _colyseus_schema.AssignableProps<_colyseus_schema.InferSchemaInstanceType<{
|
|
42
48
|
name: "string";
|
|
43
49
|
data: "string";
|
|
44
50
|
epoch: "number";
|
|
51
|
+
connected: "boolean";
|
|
45
52
|
}>>, typeof _colyseus_schema.Schema>;
|
|
46
53
|
default: MapSchema<_colyseus_schema.InferSchemaInstanceType<{
|
|
47
54
|
name: "string";
|
|
48
55
|
data: "string";
|
|
49
56
|
epoch: "number";
|
|
57
|
+
connected: "boolean";
|
|
50
58
|
}>, string>;
|
|
51
59
|
};
|
|
52
60
|
shared: {
|
package/dist/schema.js
CHANGED
|
@@ -6,7 +6,10 @@ var PlayerState = schema(
|
|
|
6
6
|
data: "string",
|
|
7
7
|
// Relay-owned discontinuity epoch. Ordinary state updates keep it stable;
|
|
8
8
|
// state:snap increments it so new SDKs discard interpolation history.
|
|
9
|
-
epoch: "number"
|
|
9
|
+
epoch: "number",
|
|
10
|
+
// Seat presence is distinct from connectivity: a dropped player keeps the
|
|
11
|
+
// seat during reconnection grace but flips connected=false immediately.
|
|
12
|
+
connected: "boolean"
|
|
10
13
|
},
|
|
11
14
|
"PlayerState"
|
|
12
15
|
);
|