twinwire 0.0.0-stage → 1.0.0
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/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +128 -2
- package/dist/browser/twinwire.js +2075 -0
- package/dist/browser/twinwire.js.map +7 -0
- package/dist/browser/twinwire.min.js +1 -0
- package/dist/browser/twinwire.mjs +2052 -0
- package/dist/browser/twinwire.mjs.map +7 -0
- package/dist/browser.d.ts +5 -0
- package/dist/browser.js +6 -0
- package/dist/browser.js.map +1 -0
- package/dist/client/client.d.ts +128 -0
- package/dist/client/client.js +470 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.js +4 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/lobby.d.ts +40 -0
- package/dist/client/lobby.js +122 -0
- package/dist/client/lobby.js.map +1 -0
- package/dist/client/rtc.d.ts +17 -0
- package/dist/client/rtc.js +102 -0
- package/dist/client/rtc.js.map +1 -0
- package/dist/core/budget.d.ts +27 -0
- package/dist/core/budget.js +39 -0
- package/dist/core/budget.js.map +1 -0
- package/dist/core/endpoint.d.ts +144 -0
- package/dist/core/endpoint.js +501 -0
- package/dist/core/endpoint.js.map +1 -0
- package/dist/core/lobby.d.ts +59 -0
- package/dist/core/lobby.js +53 -0
- package/dist/core/lobby.js.map +1 -0
- package/dist/core/protocol.d.ts +52 -0
- package/dist/core/protocol.js +100 -0
- package/dist/core/protocol.js.map +1 -0
- package/dist/core/reliable.d.ts +52 -0
- package/dist/core/reliable.js +116 -0
- package/dist/core/reliable.js.map +1 -0
- package/dist/core/rtc.d.ts +28 -0
- package/dist/core/rtc.js +21 -0
- package/dist/core/rtc.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/netcode/clock.d.ts +15 -0
- package/dist/netcode/clock.js +37 -0
- package/dist/netcode/clock.js.map +1 -0
- package/dist/netcode/codec.d.ts +10 -0
- package/dist/netcode/codec.js +95 -0
- package/dist/netcode/codec.js.map +1 -0
- package/dist/netcode/index.d.ts +9 -0
- package/dist/netcode/index.js +10 -0
- package/dist/netcode/index.js.map +1 -0
- package/dist/netcode/inputs.d.ts +7 -0
- package/dist/netcode/inputs.js +43 -0
- package/dist/netcode/inputs.js.map +1 -0
- package/dist/netcode/interpolation.d.ts +44 -0
- package/dist/netcode/interpolation.js +101 -0
- package/dist/netcode/interpolation.js.map +1 -0
- package/dist/netcode/prediction.d.ts +40 -0
- package/dist/netcode/prediction.js +97 -0
- package/dist/netcode/prediction.js.map +1 -0
- package/dist/netcode/replication.d.ts +36 -0
- package/dist/netcode/replication.js +90 -0
- package/dist/netcode/replication.js.map +1 -0
- package/dist/netcode/snapshots.d.ts +29 -0
- package/dist/netcode/snapshots.js +78 -0
- package/dist/netcode/snapshots.js.map +1 -0
- package/dist/netcode/spatial.d.ts +16 -0
- package/dist/netcode/spatial.js +63 -0
- package/dist/netcode/spatial.js.map +1 -0
- package/dist/netcode/timing.d.ts +37 -0
- package/dist/netcode/timing.js +41 -0
- package/dist/netcode/timing.js.map +1 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +5 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/lobby.d.ts +134 -0
- package/dist/server/lobby.js +462 -0
- package/dist/server/lobby.js.map +1 -0
- package/dist/server/rtc.d.ts +38 -0
- package/dist/server/rtc.js +102 -0
- package/dist/server/rtc.js.map +1 -0
- package/dist/server/server.d.ts +145 -0
- package/dist/server/server.js +457 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/turn.d.ts +22 -0
- package/dist/server/turn.js +52 -0
- package/dist/server/turn.js.map +1 -0
- package/docs/ADOPTING.md +64 -0
- package/docs/API.md +181 -0
- package/docs/ARCHITECTURE.md +101 -0
- package/docs/OPERATIONS.md +93 -0
- package/docs/RELEASE-CHECKLIST.md +25 -0
- package/package.json +100 -4
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Server-side visibility candidate lookup. Apply authorization/room filters before serializing entities. */
|
|
2
|
+
export class SpatialGrid {
|
|
3
|
+
cellSize;
|
|
4
|
+
maxEntities;
|
|
5
|
+
items = new Map();
|
|
6
|
+
cells = new Map();
|
|
7
|
+
constructor(cellSize = 32, maxEntities = 10000) {
|
|
8
|
+
this.cellSize = cellSize;
|
|
9
|
+
this.maxEntities = maxEntities;
|
|
10
|
+
if (!(cellSize > 0) || !Number.isFinite(cellSize))
|
|
11
|
+
throw new RangeError('Invalid cell size');
|
|
12
|
+
}
|
|
13
|
+
validate(p) { if (![p.x, p.y].every(v => Number.isFinite(v) && Math.abs(v) <= 1e8))
|
|
14
|
+
throw new RangeError('Invalid position'); }
|
|
15
|
+
upsert(id, p) {
|
|
16
|
+
this.validate(p);
|
|
17
|
+
if (!this.items.has(id) && this.items.size >= this.maxEntities)
|
|
18
|
+
throw new Error('Spatial entity limit');
|
|
19
|
+
const key = `${Math.floor(p.x / this.cellSize)},${Math.floor(p.y / this.cellSize)}`;
|
|
20
|
+
const old = this.items.get(id);
|
|
21
|
+
if (old && old.key !== key) {
|
|
22
|
+
const c = this.cells.get(old.key);
|
|
23
|
+
c.delete(id);
|
|
24
|
+
if (!c.size)
|
|
25
|
+
this.cells.delete(old.key);
|
|
26
|
+
}
|
|
27
|
+
let c = this.cells.get(key);
|
|
28
|
+
if (!c)
|
|
29
|
+
this.cells.set(key, c = new Set());
|
|
30
|
+
c.add(id);
|
|
31
|
+
this.items.set(id, { ...p, key });
|
|
32
|
+
}
|
|
33
|
+
remove(id) {
|
|
34
|
+
const p = this.items.get(id);
|
|
35
|
+
if (!p)
|
|
36
|
+
return;
|
|
37
|
+
this.items.delete(id);
|
|
38
|
+
const c = this.cells.get(p.key);
|
|
39
|
+
c.delete(id);
|
|
40
|
+
if (!c.size)
|
|
41
|
+
this.cells.delete(p.key);
|
|
42
|
+
}
|
|
43
|
+
query(center, radius) {
|
|
44
|
+
this.validate(center);
|
|
45
|
+
if (!Number.isFinite(radius) || radius < 0)
|
|
46
|
+
throw new RangeError('Invalid radius');
|
|
47
|
+
const minX = Math.floor((center.x - radius) / this.cellSize), maxX = Math.floor((center.x + radius) / this.cellSize);
|
|
48
|
+
const minY = Math.floor((center.y - radius) / this.cellSize), maxY = Math.floor((center.y + radius) / this.cellSize);
|
|
49
|
+
if ((maxX - minX + 1) * (maxY - minY + 1) > 4096)
|
|
50
|
+
throw new RangeError('Query exceeds cell budget');
|
|
51
|
+
const found = new Set(), r2 = radius * radius;
|
|
52
|
+
for (let x = minX; x <= maxX; x++)
|
|
53
|
+
for (let y = minY; y <= maxY; y++) {
|
|
54
|
+
for (const id of this.cells.get(`${x},${y}`) ?? []) {
|
|
55
|
+
const p = this.items.get(id);
|
|
56
|
+
if ((p.x - center.x) ** 2 + (p.y - center.y) ** 2 <= r2)
|
|
57
|
+
found.add(id);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return found;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=spatial.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spatial.js","sourceRoot":"","sources":["../../src/netcode/spatial.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare class FixedStepper {
|
|
2
|
+
readonly maxCatchUp: number;
|
|
3
|
+
tick: number;
|
|
4
|
+
overruns: number;
|
|
5
|
+
droppedMs: number;
|
|
6
|
+
private accumulator;
|
|
7
|
+
readonly dtMs: number;
|
|
8
|
+
constructor(hz?: number, maxCatchUp?: number);
|
|
9
|
+
advance(elapsedMs: number, step: (dtSeconds: number, tick: number) => void): number;
|
|
10
|
+
get alpha(): number;
|
|
11
|
+
}
|
|
12
|
+
export declare const PROFILES: Readonly<{
|
|
13
|
+
social: {
|
|
14
|
+
simulationHz: number;
|
|
15
|
+
sendHz: number;
|
|
16
|
+
bytesPerSecond: number;
|
|
17
|
+
interpolationMinMs: number;
|
|
18
|
+
};
|
|
19
|
+
action: {
|
|
20
|
+
simulationHz: number;
|
|
21
|
+
sendHz: number;
|
|
22
|
+
bytesPerSecond: number;
|
|
23
|
+
interpolationMinMs: number;
|
|
24
|
+
};
|
|
25
|
+
strategy: {
|
|
26
|
+
simulationHz: number;
|
|
27
|
+
sendHz: number;
|
|
28
|
+
bytesPerSecond: number;
|
|
29
|
+
interpolationMinMs: number;
|
|
30
|
+
};
|
|
31
|
+
turnBased: {
|
|
32
|
+
simulationHz: number;
|
|
33
|
+
sendHz: number;
|
|
34
|
+
bytesPerSecond: number;
|
|
35
|
+
interpolationMinMs: number;
|
|
36
|
+
};
|
|
37
|
+
}>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export class FixedStepper {
|
|
2
|
+
maxCatchUp;
|
|
3
|
+
tick = 0;
|
|
4
|
+
overruns = 0;
|
|
5
|
+
droppedMs = 0;
|
|
6
|
+
accumulator = 0;
|
|
7
|
+
dtMs;
|
|
8
|
+
constructor(hz = 30, maxCatchUp = 4) {
|
|
9
|
+
this.maxCatchUp = maxCatchUp;
|
|
10
|
+
if (!(hz > 0) || !Number.isFinite(hz) || !Number.isInteger(maxCatchUp) || maxCatchUp < 1)
|
|
11
|
+
throw new RangeError('Invalid tick policy');
|
|
12
|
+
this.dtMs = 1000 / hz;
|
|
13
|
+
}
|
|
14
|
+
advance(elapsedMs, step) {
|
|
15
|
+
if (!Number.isFinite(elapsedMs) || elapsedMs < 0)
|
|
16
|
+
throw new RangeError('Invalid elapsed time');
|
|
17
|
+
this.accumulator += elapsedMs;
|
|
18
|
+
let n = 0;
|
|
19
|
+
while (this.accumulator + 1e-8 >= this.dtMs && n < this.maxCatchUp) {
|
|
20
|
+
this.accumulator -= this.dtMs;
|
|
21
|
+
this.tick++;
|
|
22
|
+
n++;
|
|
23
|
+
step(this.dtMs / 1000, this.tick);
|
|
24
|
+
}
|
|
25
|
+
if (this.accumulator >= this.dtMs) {
|
|
26
|
+
const discard = Math.floor(this.accumulator / this.dtMs) * this.dtMs;
|
|
27
|
+
this.accumulator -= discard;
|
|
28
|
+
this.droppedMs += discard;
|
|
29
|
+
this.overruns++;
|
|
30
|
+
}
|
|
31
|
+
return n;
|
|
32
|
+
}
|
|
33
|
+
get alpha() { return Math.max(0, this.accumulator / this.dtMs); }
|
|
34
|
+
}
|
|
35
|
+
export const PROFILES = Object.freeze({
|
|
36
|
+
social: { simulationHz: 20, sendHz: 20, bytesPerSecond: 12 * 1024, interpolationMinMs: 50 },
|
|
37
|
+
action: { simulationHz: 60, sendHz: 30, bytesPerSecond: 24 * 1024, interpolationMinMs: 35 },
|
|
38
|
+
strategy: { simulationHz: 10, sendHz: 10, bytesPerSecond: 12 * 1024, interpolationMinMs: 100 },
|
|
39
|
+
turnBased: { simulationHz: 0, sendHz: 0, bytesPerSecond: 8 * 1024, interpolationMinMs: 0 },
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=timing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timing.js","sourceRoot":"","sources":["../../src/netcode/timing.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,YAAY;IAIO;IAH9B,IAAI,GAAG,CAAC,CAAC;IAAC,QAAQ,GAAG,CAAC,CAAC;IAAC,SAAS,GAAG,CAAC,CAAC;IAC9B,WAAW,GAAG,CAAC,CAAC;IACf,IAAI,CAAS;IACtB,YAAY,EAAE,GAAG,EAAE,EAAW,aAAa,CAAC;QAAd,eAAU,GAAV,UAAU,CAAI;QAC1C,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,UAAU,GAAG,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,qBAAqB,CAAC,CAAC;QACtI,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,EAAE,CAAC;IACxB,CAAC;IACD,OAAO,CAAC,SAAiB,EAAE,IAA+C;QACxE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,sBAAsB,CAAC,CAAC;QAC/F,IAAI,CAAC,WAAW,IAAI,SAAS,CAAC;QAC9B,IAAI,CAAC,GAAG,CAAC,CAAC;QACV,OAAO,IAAI,CAAC,WAAW,GAAG,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;YACnE,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,IAAI,CAAC;YAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAAC,CAAC,EAAE,CAAC;YAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACrF,CAAC;QACD,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAClC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;YACrE,IAAI,CAAC,WAAW,IAAI,OAAO,CAAC;YAAC,IAAI,CAAC,SAAS,IAAI,OAAO,CAAC;YAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC1E,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,KAAK,KAAK,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;CAClE;AACD,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC;IACpC,MAAM,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,GAAG,IAAI,EAAE,kBAAkB,EAAE,EAAE,EAAE;IAC3F,MAAM,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,GAAG,IAAI,EAAE,kBAAkB,EAAE,EAAE,EAAE;IAC3F,QAAQ,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,GAAG,IAAI,EAAE,kBAAkB,EAAE,GAAG,EAAE;IAC9F,SAAS,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC,GAAG,IAAI,EAAE,kBAAkB,EAAE,CAAC,EAAE;CAC3F,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { StateMessage } from '../core/endpoint.js';
|
|
2
|
+
import type { Json } from '../core/protocol.js';
|
|
3
|
+
import { type CreateRequest, type JoinRequest, type QuickPlayRequest, type RoomView, type Visibility } from '../core/lobby.js';
|
|
4
|
+
import type { GameServer, GameSession } from './server.js';
|
|
5
|
+
export interface LobbyOptions {
|
|
6
|
+
/** Seats per room unless a request asks for fewer (default 4, at most 64). */
|
|
7
|
+
maxMembers?: number;
|
|
8
|
+
codeLength?: number;
|
|
9
|
+
/** A room with no members left closes after this long (default 30 s; a later join by code cancels it). */
|
|
10
|
+
emptyCloseMs?: number;
|
|
11
|
+
maxRooms?: number;
|
|
12
|
+
/** Rooms one address may create per hour (default 30). */
|
|
13
|
+
maxRoomsPerIpPerHour?: number;
|
|
14
|
+
/** Phases a code can join (default lobby and playing: drop-in). */
|
|
15
|
+
joinablePhases?: string[];
|
|
16
|
+
/** Phases quick play may drop a player into (default ['lobby']). */
|
|
17
|
+
quickPlayPhases?: string[];
|
|
18
|
+
/** When a room is full, free the seat of whoever has been disconnected longest (default true). */
|
|
19
|
+
evictDisconnected?: boolean;
|
|
20
|
+
/** Ready-up: once every connected member is ready (and at least minMembers), count down and start. false: no ready. */
|
|
21
|
+
ready?: false | {
|
|
22
|
+
countdownMs?: number;
|
|
23
|
+
minMembers?: number;
|
|
24
|
+
};
|
|
25
|
+
allowHostKick?: boolean;
|
|
26
|
+
/** Member meta keys a client may set (setMeta, memberMeta on join). Default: any. Keys the server sets stay out of this list. */
|
|
27
|
+
clientMetaKeys?: string[];
|
|
28
|
+
/** Return an error message to refuse. */
|
|
29
|
+
canCreate?: (session: GameSession, request: CreateRequest) => string | null | void;
|
|
30
|
+
canJoin?: (room: Room, session: GameSession, request: JoinRequest | QuickPlayRequest) => string | null | void;
|
|
31
|
+
}
|
|
32
|
+
export interface Member {
|
|
33
|
+
readonly session: GameSession;
|
|
34
|
+
readonly room: Room;
|
|
35
|
+
readonly seat: number;
|
|
36
|
+
readonly joinedAt: number;
|
|
37
|
+
name: string;
|
|
38
|
+
ready: boolean;
|
|
39
|
+
connected: boolean;
|
|
40
|
+
disconnectedAt: number;
|
|
41
|
+
meta: Record<string, Json>;
|
|
42
|
+
/** Free for the application (player id in the simulation, score). */
|
|
43
|
+
data: unknown;
|
|
44
|
+
}
|
|
45
|
+
export declare class Room {
|
|
46
|
+
readonly lobby: Lobby;
|
|
47
|
+
readonly code: string;
|
|
48
|
+
readonly mode: string;
|
|
49
|
+
visibility: Visibility;
|
|
50
|
+
readonly maxMembers: number;
|
|
51
|
+
phase: string;
|
|
52
|
+
meta: Record<string, Json>;
|
|
53
|
+
/** Free for the application (its simulation, world). */
|
|
54
|
+
data: unknown;
|
|
55
|
+
readonly members: Map<string, Member>;
|
|
56
|
+
host: Member | null;
|
|
57
|
+
countdownEndsAt: number | null;
|
|
58
|
+
readonly createdAt: number;
|
|
59
|
+
emptySince: number | null;
|
|
60
|
+
closed: boolean;
|
|
61
|
+
/** @internal */ countdown?: ReturnType<typeof setTimeout>;
|
|
62
|
+
constructor(lobby: Lobby, code: string, mode: string, visibility: Visibility, maxMembers: number, meta: Record<string, Json>);
|
|
63
|
+
get connectedCount(): number;
|
|
64
|
+
memberAt(seat: number): Member | undefined;
|
|
65
|
+
view(viewer: Member | null): RoomView;
|
|
66
|
+
/** Reliable event to every member (disconnected ones get it on resume). Returns members whose outbox was full. */
|
|
67
|
+
broadcast(data: Json, except?: Member): Member[];
|
|
68
|
+
send(member: Member, data: Json): boolean;
|
|
69
|
+
setPhase(phase: string): void;
|
|
70
|
+
setMeta(patch: Record<string, Json>): void;
|
|
71
|
+
setVisibility(v: Visibility): void;
|
|
72
|
+
/** Start now (skips the ready countdown). */
|
|
73
|
+
start(): void;
|
|
74
|
+
close(reason?: string): void;
|
|
75
|
+
}
|
|
76
|
+
export declare class Lobby {
|
|
77
|
+
readonly server: GameServer;
|
|
78
|
+
readonly rooms: Map<string, Room>;
|
|
79
|
+
private bySession;
|
|
80
|
+
private creations;
|
|
81
|
+
private timer;
|
|
82
|
+
private off;
|
|
83
|
+
readonly options: Required<Omit<LobbyOptions, 'canCreate' | 'canJoin' | 'ready' | 'clientMetaKeys'>> & Pick<LobbyOptions, 'canCreate' | 'canJoin' | 'ready' | 'clientMetaKeys'>;
|
|
84
|
+
/** A room was made by request; `request.init` carries the creator's setup data (validate it). */
|
|
85
|
+
onCreate: (room: Room, creator: GameSession, request: CreateRequest) => void;
|
|
86
|
+
onJoin: (room: Room, member: Member) => void;
|
|
87
|
+
onLeave: (room: Room, member: Member, reason: string) => void;
|
|
88
|
+
onDisconnect: (room: Room, member: Member) => void;
|
|
89
|
+
/** A member's session is back on a new connection: after a drop, or a reloaded page taking it over. */
|
|
90
|
+
onReconnect: (room: Room, member: Member) => void;
|
|
91
|
+
onStart: (room: Room) => void;
|
|
92
|
+
onClose: (room: Room, reason: string) => void;
|
|
93
|
+
/** Application events (anything that is not a lobby request). room/member are null outside a room. */
|
|
94
|
+
onEvent: (room: Room | null, member: Member | null, data: Json, session: GameSession) => void;
|
|
95
|
+
onState: (room: Room | null, member: Member | null, state: StateMessage, session: GameSession) => void;
|
|
96
|
+
onError: (error: Error) => void;
|
|
97
|
+
constructor(server: GameServer, o?: LobbyOptions);
|
|
98
|
+
/** @internal */ report(e: unknown): void;
|
|
99
|
+
memberOf(session: GameSession): Member | undefined;
|
|
100
|
+
roomOf(session: GameSession): Room | undefined;
|
|
101
|
+
dispose(): void;
|
|
102
|
+
private attach;
|
|
103
|
+
private disconnected;
|
|
104
|
+
private safe;
|
|
105
|
+
private event;
|
|
106
|
+
private member;
|
|
107
|
+
private send;
|
|
108
|
+
/** A client's meta, cleaned and limited to the keys clients may set (null: invalid). */
|
|
109
|
+
private clientMeta;
|
|
110
|
+
private newCode;
|
|
111
|
+
private rateOk;
|
|
112
|
+
/** Create a room on the server's own initiative (no creator). */
|
|
113
|
+
createRoom(o?: {
|
|
114
|
+
mode?: string;
|
|
115
|
+
visibility?: Visibility;
|
|
116
|
+
maxMembers?: number;
|
|
117
|
+
meta?: Record<string, Json>;
|
|
118
|
+
}): Room;
|
|
119
|
+
private create;
|
|
120
|
+
private join;
|
|
121
|
+
private quickPlay;
|
|
122
|
+
private hasSeat;
|
|
123
|
+
private seat;
|
|
124
|
+
private pickHost;
|
|
125
|
+
private removeMember;
|
|
126
|
+
private setReady;
|
|
127
|
+
/** @internal Everyone gets the new room view; a ready room starts its countdown. */
|
|
128
|
+
changed(room: Room, skip?: Member): void;
|
|
129
|
+
private checkReady;
|
|
130
|
+
/** @internal */ cancelCountdown(room: Room): void;
|
|
131
|
+
/** @internal */ startRoom(room: Room): void;
|
|
132
|
+
/** @internal */ closeRoom(room: Room, reason: string): void;
|
|
133
|
+
private sweep;
|
|
134
|
+
}
|