castle-web-sdk 0.4.36 → 0.4.38
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/dist/castle.d.ts +3 -3
- package/dist/commands.d.ts +46 -1
- package/dist/multiplayer.d.ts +85 -2
- package/dist/multiplayer.js +422 -12
- package/dist/multiplayerProtocol.d.ts +54 -2
- package/dist/multiplayerVoice.d.ts +26 -2
- package/dist/multiplayerVoice.js +118 -3
- package/dist/server/platformHandle.d.ts +54 -0
- package/dist/server/wrapper.d.ts +79 -1
- package/dist/server/wrapper.js +334 -0
- package/dist/transport.js +1 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { VoiceEvent, VoiceJoinStatus, VoiceMixEntry, VoiceState } from "./commands";
|
|
1
|
+
import type { VoiceEvent, VoiceJoinStatus, VoiceListener, VoiceMixEntry, VoiceSpatialOptions, VoiceState, VoiceVector } from "./commands";
|
|
2
2
|
import type { MultiplayerPlayer } from "./multiplayerProtocol";
|
|
3
3
|
export declare const VOICE_MIX_INTERVAL_MS = 100;
|
|
4
|
-
export type { VoiceJoinStatus, VoiceMixEntry, VoiceState };
|
|
4
|
+
export type { VoiceJoinStatus, VoiceListener, VoiceMixEntry, VoiceSpatialOptions, VoiceState, VoiceVector, };
|
|
5
5
|
export interface MultiplayerVoice {
|
|
6
6
|
/** "unavailable" when this host, deck, session or player can't have voice. */
|
|
7
7
|
readonly state: VoiceState;
|
|
@@ -18,6 +18,19 @@ export interface MultiplayerVoice {
|
|
|
18
18
|
* full volume, centered). Sent at most ten times a second; the latest values win.
|
|
19
19
|
*/
|
|
20
20
|
setMix(mix: Record<string, VoiceMixEntry>): void;
|
|
21
|
+
/**
|
|
22
|
+
* 3D voice: where the listener (usually the local player's camera) is and faces, in
|
|
23
|
+
* the deck's world units, y up. With positions set, voices come from their place in
|
|
24
|
+
* the world (distance, left/right, front/behind, above/below; best on headphones).
|
|
25
|
+
*/
|
|
26
|
+
setListener(listener: VoiceListener): void;
|
|
27
|
+
/**
|
|
28
|
+
* Where each player's voice comes from, keyed by playerId. `null` returns a player
|
|
29
|
+
* to plain gain and pan. Their `setMix` gain still applies; their pan does not.
|
|
30
|
+
*/
|
|
31
|
+
setPositions(positions: Record<string, VoiceVector | null>): void;
|
|
32
|
+
/** How voices fade with distance. Defaults: inverse, refDistance 1, maxDistance 100, rolloff 1. */
|
|
33
|
+
setSpatialOptions(options: VoiceSpatialOptions): void;
|
|
21
34
|
onState(callback: (state: VoiceState) => void): () => void;
|
|
22
35
|
/** The playerIds speaking right now; called whenever that set changes. */
|
|
23
36
|
onSpeaking(callback: (playerIds: string[]) => void): () => void;
|
|
@@ -39,6 +52,12 @@ export declare class MultiplayerVoiceImpl implements MultiplayerVoice {
|
|
|
39
52
|
private speaking;
|
|
40
53
|
private pendingMix;
|
|
41
54
|
private sentMix;
|
|
55
|
+
private pendingListener;
|
|
56
|
+
private pendingPositions;
|
|
57
|
+
private pendingOptions;
|
|
58
|
+
private sentListener;
|
|
59
|
+
private sentPositions;
|
|
60
|
+
private sentOptions;
|
|
42
61
|
private mixTimer;
|
|
43
62
|
private lastMixSentAt;
|
|
44
63
|
private disposed;
|
|
@@ -51,6 +70,9 @@ export declare class MultiplayerVoiceImpl implements MultiplayerVoice {
|
|
|
51
70
|
private askToJoin;
|
|
52
71
|
leave(): void;
|
|
53
72
|
setMix(mix: Record<string, VoiceMixEntry>): void;
|
|
73
|
+
setListener(listener: VoiceListener): void;
|
|
74
|
+
setPositions(positions: Record<string, VoiceVector | null>): void;
|
|
75
|
+
setSpatialOptions(options: VoiceSpatialOptions): void;
|
|
54
76
|
onState(callback: (state: VoiceState) => void): () => void;
|
|
55
77
|
onSpeaking(callback: (playerIds: string[]) => void): () => void;
|
|
56
78
|
receive(event: VoiceEvent): void;
|
|
@@ -62,6 +84,8 @@ export declare class MultiplayerVoiceImpl implements MultiplayerVoice {
|
|
|
62
84
|
private sendLeave;
|
|
63
85
|
private setState;
|
|
64
86
|
private emitSpeaking;
|
|
87
|
+
private hasPending;
|
|
65
88
|
private scheduleMix;
|
|
66
89
|
private flushMix;
|
|
90
|
+
private takeSpatial;
|
|
67
91
|
}
|
package/dist/multiplayerVoice.js
CHANGED
|
@@ -31,6 +31,13 @@ export class MultiplayerVoiceImpl {
|
|
|
31
31
|
speaking = [];
|
|
32
32
|
pendingMix = {};
|
|
33
33
|
sentMix = {};
|
|
34
|
+
// Spatial state: pending (not yet sent) and sent (re-sent when a call restarts).
|
|
35
|
+
pendingListener = null;
|
|
36
|
+
pendingPositions = {};
|
|
37
|
+
pendingOptions = null;
|
|
38
|
+
sentListener = null;
|
|
39
|
+
sentPositions = {};
|
|
40
|
+
sentOptions = null;
|
|
34
41
|
mixTimer = null;
|
|
35
42
|
lastMixSentAt = 0;
|
|
36
43
|
disposed = false;
|
|
@@ -163,6 +170,45 @@ export class MultiplayerVoiceImpl {
|
|
|
163
170
|
}
|
|
164
171
|
this.scheduleMix();
|
|
165
172
|
}
|
|
173
|
+
setListener(listener) {
|
|
174
|
+
if (this.disposed || !listener || typeof listener !== "object")
|
|
175
|
+
return;
|
|
176
|
+
const position = vector(listener.position);
|
|
177
|
+
const forward = vector(listener.forward);
|
|
178
|
+
const up = listener.up === undefined ? undefined : vector(listener.up);
|
|
179
|
+
// A zero direction faces nowhere (a player standing still, say): keep the last
|
|
180
|
+
// listener rather than send one the host would drop.
|
|
181
|
+
if (!position || !forward || up === null || isZero(forward) || (up && isZero(up)))
|
|
182
|
+
return;
|
|
183
|
+
this.pendingListener = up ? { position, forward, up } : { position, forward };
|
|
184
|
+
this.scheduleMix();
|
|
185
|
+
}
|
|
186
|
+
setPositions(positions) {
|
|
187
|
+
if (this.disposed || !positions || typeof positions !== "object")
|
|
188
|
+
return;
|
|
189
|
+
const players = this.access.players();
|
|
190
|
+
for (const [playerId, value] of Object.entries(positions)) {
|
|
191
|
+
const player = players.find((candidate) => candidate.playerId === playerId);
|
|
192
|
+
if (!player)
|
|
193
|
+
continue;
|
|
194
|
+
if (value === null) {
|
|
195
|
+
this.pendingPositions[player.userId] = null;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const position = vector(value);
|
|
199
|
+
if (position)
|
|
200
|
+
this.pendingPositions[player.userId] = position;
|
|
201
|
+
}
|
|
202
|
+
this.scheduleMix();
|
|
203
|
+
}
|
|
204
|
+
setSpatialOptions(options) {
|
|
205
|
+
if (this.disposed || !options || typeof options !== "object")
|
|
206
|
+
return;
|
|
207
|
+
// Cumulative: the host fills every missing field with its default, so each send
|
|
208
|
+
// carries everything the deck has set so far.
|
|
209
|
+
this.pendingOptions = { ...this.sentOptions, ...this.pendingOptions, ...options };
|
|
210
|
+
this.scheduleMix();
|
|
211
|
+
}
|
|
166
212
|
onState(callback) {
|
|
167
213
|
this.states.add(callback);
|
|
168
214
|
callListener(callback, this.currentState);
|
|
@@ -198,6 +244,14 @@ export class MultiplayerVoiceImpl {
|
|
|
198
244
|
if (!userIds.has(userId))
|
|
199
245
|
delete this.sentMix[userId];
|
|
200
246
|
}
|
|
247
|
+
for (const userId of Object.keys(this.pendingPositions)) {
|
|
248
|
+
if (!userIds.has(userId))
|
|
249
|
+
delete this.pendingPositions[userId];
|
|
250
|
+
}
|
|
251
|
+
for (const userId of Object.keys(this.sentPositions)) {
|
|
252
|
+
if (!userIds.has(userId))
|
|
253
|
+
delete this.sentPositions[userId];
|
|
254
|
+
}
|
|
201
255
|
this.emitSpeaking();
|
|
202
256
|
}
|
|
203
257
|
// The game connection ended: voice for its session goes with it.
|
|
@@ -230,6 +284,12 @@ export class MultiplayerVoiceImpl {
|
|
|
230
284
|
this.speakingUserIds = [];
|
|
231
285
|
this.pendingMix = {};
|
|
232
286
|
this.sentMix = {};
|
|
287
|
+
this.pendingListener = null;
|
|
288
|
+
this.pendingPositions = {};
|
|
289
|
+
this.pendingOptions = null;
|
|
290
|
+
this.sentListener = null;
|
|
291
|
+
this.sentPositions = {};
|
|
292
|
+
this.sentOptions = null;
|
|
233
293
|
this.emitSpeaking();
|
|
234
294
|
}
|
|
235
295
|
sendLeave(sessionId) {
|
|
@@ -249,6 +309,12 @@ export class MultiplayerVoiceImpl {
|
|
|
249
309
|
for (const userId of Object.keys(this.sentMix)) {
|
|
250
310
|
this.pendingMix[userId] ??= {};
|
|
251
311
|
}
|
|
312
|
+
this.pendingListener ??= this.sentListener;
|
|
313
|
+
this.pendingOptions ??= this.sentOptions;
|
|
314
|
+
for (const [userId, position] of Object.entries(this.sentPositions)) {
|
|
315
|
+
if (!(userId in this.pendingPositions))
|
|
316
|
+
this.pendingPositions[userId] = position;
|
|
317
|
+
}
|
|
252
318
|
this.scheduleMix();
|
|
253
319
|
}
|
|
254
320
|
}
|
|
@@ -265,8 +331,14 @@ export class MultiplayerVoiceImpl {
|
|
|
265
331
|
for (const callback of this.speakers)
|
|
266
332
|
callListener(callback, playerIds.slice());
|
|
267
333
|
}
|
|
334
|
+
hasPending() {
|
|
335
|
+
return (Object.keys(this.pendingMix).length > 0 ||
|
|
336
|
+
Object.keys(this.pendingPositions).length > 0 ||
|
|
337
|
+
this.pendingListener !== null ||
|
|
338
|
+
this.pendingOptions !== null);
|
|
339
|
+
}
|
|
268
340
|
scheduleMix() {
|
|
269
|
-
if (this.mixTimer !== null ||
|
|
341
|
+
if (this.mixTimer !== null || !this.hasPending())
|
|
270
342
|
return;
|
|
271
343
|
const wait = Math.max(0, this.lastMixSentAt + VOICE_MIX_INTERVAL_MS - Date.now());
|
|
272
344
|
this.mixTimer = setTimeout(() => {
|
|
@@ -290,11 +362,54 @@ export class MultiplayerVoiceImpl {
|
|
|
290
362
|
mix[userId] = full;
|
|
291
363
|
}
|
|
292
364
|
this.pendingMix = {};
|
|
293
|
-
|
|
365
|
+
const spatial = this.takeSpatial(userIds);
|
|
366
|
+
if (Object.keys(mix).length === 0 && !spatial)
|
|
294
367
|
return;
|
|
295
368
|
this.lastMixSentAt = Date.now();
|
|
296
|
-
|
|
369
|
+
if (Object.keys(mix).length > 0) {
|
|
370
|
+
void hostRequest("voice.setMix", { sessionId: this.voiceSessionId, mix }).catch(() => { });
|
|
371
|
+
}
|
|
372
|
+
if (spatial) {
|
|
373
|
+
void hostRequest("voice.setSpatial", { sessionId: this.voiceSessionId, ...spatial }).catch(() => { });
|
|
374
|
+
}
|
|
297
375
|
}
|
|
376
|
+
takeSpatial(userIds) {
|
|
377
|
+
const update = {};
|
|
378
|
+
if (this.pendingListener) {
|
|
379
|
+
update.listener = this.pendingListener;
|
|
380
|
+
this.sentListener = this.pendingListener;
|
|
381
|
+
}
|
|
382
|
+
if (this.pendingOptions) {
|
|
383
|
+
update.options = this.pendingOptions;
|
|
384
|
+
this.sentOptions = this.pendingOptions;
|
|
385
|
+
}
|
|
386
|
+
const positions = {};
|
|
387
|
+
for (const [userId, position] of Object.entries(this.pendingPositions)) {
|
|
388
|
+
if (!userIds.has(userId))
|
|
389
|
+
continue;
|
|
390
|
+
positions[userId] = position;
|
|
391
|
+
if (position)
|
|
392
|
+
this.sentPositions[userId] = position;
|
|
393
|
+
else
|
|
394
|
+
delete this.sentPositions[userId];
|
|
395
|
+
}
|
|
396
|
+
if (Object.keys(positions).length > 0)
|
|
397
|
+
update.positions = positions;
|
|
398
|
+
this.pendingListener = null;
|
|
399
|
+
this.pendingOptions = null;
|
|
400
|
+
this.pendingPositions = {};
|
|
401
|
+
return update.listener || update.options || update.positions ? update : null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function vector(value) {
|
|
405
|
+
return Array.isArray(value) &&
|
|
406
|
+
value.length === 3 &&
|
|
407
|
+
value.every((component) => typeof component === "number" && Number.isFinite(component))
|
|
408
|
+
? [value[0], value[1], value[2]]
|
|
409
|
+
: null;
|
|
410
|
+
}
|
|
411
|
+
function isZero(value) {
|
|
412
|
+
return value[0] === 0 && value[1] === 0 && value[2] === 0;
|
|
298
413
|
}
|
|
299
414
|
function clamp(value, min, max) {
|
|
300
415
|
return Math.min(max, Math.max(min, value));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Json } from "../types";
|
|
1
2
|
export type MultiplayerSessionMode = "named" | "party" | "public";
|
|
2
3
|
export interface PlayerIdentity {
|
|
3
4
|
userId: string;
|
|
@@ -11,6 +12,15 @@ export interface PlatformSessionConfig {
|
|
|
11
12
|
readonly mode: MultiplayerSessionMode;
|
|
12
13
|
readonly tickRate: number;
|
|
13
14
|
readonly maxPlayers: number;
|
|
15
|
+
readonly world?: PlatformWorldSnapshot;
|
|
16
|
+
}
|
|
17
|
+
export interface PlatformWorldCheckpoint {
|
|
18
|
+
version: number;
|
|
19
|
+
value: Json;
|
|
20
|
+
}
|
|
21
|
+
export interface PlatformWorldSnapshot {
|
|
22
|
+
revision: string;
|
|
23
|
+
checkpoint: PlatformWorldCheckpoint | null;
|
|
14
24
|
}
|
|
15
25
|
export interface PlatformHandle {
|
|
16
26
|
send(connId: string, text: string): void;
|
|
@@ -24,6 +34,9 @@ export interface PlatformHandle {
|
|
|
24
34
|
on(event: "binaryMessage", callback: (connId: string, bytes: Uint8Array) => unknown): void;
|
|
25
35
|
on(event: "replaced", callback: () => unknown): void;
|
|
26
36
|
on(event: "shutdown", callback: (reason?: string) => unknown): void;
|
|
37
|
+
on(event: "worldCharacterTurn", callback: (request: PlatformWorldCharacterTurn) => unknown): void;
|
|
38
|
+
on(event: "worldCharacterCommand", callback: (request: PlatformWorldCharacterCommand) => unknown): void;
|
|
39
|
+
on(event: "worldCharacterRead", callback: (request: PlatformWorldCharacterRead) => unknown): void;
|
|
27
40
|
/**
|
|
28
41
|
* Deck storage. Strings in and strings out, like the rest of this handle, so a
|
|
29
42
|
* runtime other than the Node shim can implement it without knowing what storage is.
|
|
@@ -34,5 +47,46 @@ export interface PlatformHandle {
|
|
|
34
47
|
storage?(op: string, argsJson: string): Promise<string>;
|
|
35
48
|
/** False when the host cannot broker storage, so a call can fail at once. */
|
|
36
49
|
readonly storageEnabled?: boolean;
|
|
50
|
+
/** Persistent-world checkpoint broker. The host stamps the hidden world binding. */
|
|
51
|
+
worldCheckpointRead?(): Promise<string>;
|
|
52
|
+
worldCheckpointCommit?(argsJson: string): Promise<string>;
|
|
53
|
+
worldCharacterSend?(grantId: string): Promise<string>;
|
|
54
|
+
worldCharacterCommand?(grantId: string): Promise<string>;
|
|
55
|
+
worldCharacterRead?(grantId: string): Promise<string>;
|
|
56
|
+
worldCharacterReject?(grantId: string, code?: string): void;
|
|
37
57
|
readonly config: PlatformSessionConfig;
|
|
38
58
|
}
|
|
59
|
+
export interface PlatformWorldCharacterCommand {
|
|
60
|
+
grantId: string;
|
|
61
|
+
requestId: string;
|
|
62
|
+
operationId: string;
|
|
63
|
+
speaker: PlayerIdentity & {
|
|
64
|
+
playerId: string;
|
|
65
|
+
};
|
|
66
|
+
command: unknown;
|
|
67
|
+
}
|
|
68
|
+
export interface PlatformWorldCharacterRead {
|
|
69
|
+
grantId: string;
|
|
70
|
+
requestId: string;
|
|
71
|
+
speaker: PlayerIdentity & {
|
|
72
|
+
playerId: string;
|
|
73
|
+
};
|
|
74
|
+
query: unknown;
|
|
75
|
+
}
|
|
76
|
+
export interface PlatformWorldCharacterTurn {
|
|
77
|
+
grantId: string;
|
|
78
|
+
requestId: string;
|
|
79
|
+
operationId: string;
|
|
80
|
+
speaker: PlayerIdentity & {
|
|
81
|
+
playerId: string;
|
|
82
|
+
};
|
|
83
|
+
characterId: string;
|
|
84
|
+
input: string;
|
|
85
|
+
audience: {
|
|
86
|
+
disclosure: "participants" | "world";
|
|
87
|
+
players: Array<PlayerIdentity & {
|
|
88
|
+
playerId: string;
|
|
89
|
+
}>;
|
|
90
|
+
characterIds: string[];
|
|
91
|
+
};
|
|
92
|
+
}
|
package/dist/server/wrapper.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { MultiplayerSessionMode, PlatformHandle, PlayerIdentity } from "./platformHandle";
|
|
2
|
+
import type { Json } from "../types";
|
|
2
3
|
import { type ServerStorage } from "./storage";
|
|
3
|
-
export type { MultiplayerSessionMode, PlatformHandle, PlatformSessionConfig, PlayerIdentity, } from "./platformHandle";
|
|
4
|
+
export type { MultiplayerSessionMode, PlatformHandle, PlatformSessionConfig, PlatformWorldCheckpoint, PlatformWorldSnapshot, PlayerIdentity, } from "./platformHandle";
|
|
4
5
|
export type { CharacterKnowledgeSource, CompleteCharacterBeatInput, DeliverCharacterKnowledgeInput, RecordCharacterEventInput, SetCharacterGameStateInput, ShareCharacterClaimInput, ServerStorage, ServerStorageBoard, ServerStorageBoardScore, ServerStorageListEntry, ServerStorageListOptions, ServerStorageListPage, ServerPlaythrough, ServerPlaythroughCharacters, ServerCharacterHistoryOptions, ServerStorageScope, } from "./storage";
|
|
5
6
|
export interface MultiplayerServerPlayer extends PlayerIdentity {
|
|
6
7
|
playerId: string;
|
|
@@ -18,12 +19,86 @@ export interface MultiplayerServerSession {
|
|
|
18
19
|
* when its last player leaves and its memory goes with it.
|
|
19
20
|
*/
|
|
20
21
|
readonly storage: ServerStorage;
|
|
22
|
+
/** A durable checkpoint for an opted-in persistent destination. */
|
|
23
|
+
readonly world: MultiplayerServerWorld | null;
|
|
21
24
|
/** Sends bytes as a binary message and all other payloads as JSON. */
|
|
22
25
|
send(playerId: string, data: unknown): void;
|
|
23
26
|
/** Broadcasts bytes as a binary message and all other payloads as JSON. */
|
|
24
27
|
broadcast(data: unknown, options?: MultiplayerBroadcastOptions): void;
|
|
25
28
|
disconnect(playerId: string, reason?: string): void;
|
|
26
29
|
}
|
|
30
|
+
export interface MultiplayerWorldCheckpoint {
|
|
31
|
+
version: number;
|
|
32
|
+
value: Json;
|
|
33
|
+
}
|
|
34
|
+
export interface MultiplayerWorldSnapshot {
|
|
35
|
+
revision: string;
|
|
36
|
+
checkpoint: MultiplayerWorldCheckpoint | null;
|
|
37
|
+
}
|
|
38
|
+
export interface MultiplayerWorldCommitInput {
|
|
39
|
+
expectedRevision: string;
|
|
40
|
+
operationId: string;
|
|
41
|
+
checkpoint: MultiplayerWorldCheckpoint;
|
|
42
|
+
}
|
|
43
|
+
export interface MultiplayerWorldCommitReceipt {
|
|
44
|
+
revision: string;
|
|
45
|
+
replayed: boolean;
|
|
46
|
+
operationId: string;
|
|
47
|
+
}
|
|
48
|
+
export interface MultiplayerServerWorld {
|
|
49
|
+
/** The checkpoint acquired before this runtime started. */
|
|
50
|
+
readonly snapshot: MultiplayerWorldSnapshot;
|
|
51
|
+
/** Reads the latest durable checkpoint, for example after a revision conflict. */
|
|
52
|
+
read(): Promise<MultiplayerWorldSnapshot>;
|
|
53
|
+
/** Commits exactly against expectedRevision; conflicts never partially write. */
|
|
54
|
+
commit(input: MultiplayerWorldCommitInput): Promise<MultiplayerWorldCommitReceipt>;
|
|
55
|
+
readonly characters: MultiplayerServerWorldCharacters;
|
|
56
|
+
}
|
|
57
|
+
export interface MultiplayerServerWorldCharacters {
|
|
58
|
+
publicInspect<T = unknown>(): Promise<T>;
|
|
59
|
+
publicRelay(input: {
|
|
60
|
+
operationId: string;
|
|
61
|
+
sourceId: string;
|
|
62
|
+
transmittingCharacterId: string;
|
|
63
|
+
recipientCharacterIds: string[];
|
|
64
|
+
}): Promise<unknown>;
|
|
65
|
+
}
|
|
66
|
+
export interface MultiplayerWorldCharacterCommandRequest {
|
|
67
|
+
readonly requestId: string;
|
|
68
|
+
readonly operationId: string;
|
|
69
|
+
readonly speaker: MultiplayerServerPlayer;
|
|
70
|
+
readonly command: Readonly<Record<string, unknown>>;
|
|
71
|
+
apply(): Promise<unknown>;
|
|
72
|
+
reject(code?: string): void;
|
|
73
|
+
}
|
|
74
|
+
export interface MultiplayerWorldCharacterReadRequest {
|
|
75
|
+
readonly requestId: string;
|
|
76
|
+
readonly speaker: MultiplayerServerPlayer;
|
|
77
|
+
readonly query: Readonly<Record<string, unknown>>;
|
|
78
|
+
read<T = unknown>(): Promise<T>;
|
|
79
|
+
reject(code?: string): void;
|
|
80
|
+
}
|
|
81
|
+
export interface MultiplayerWorldCharacterAdmission {
|
|
82
|
+
operationId: string;
|
|
83
|
+
status: "queued" | "completed" | "interrupted";
|
|
84
|
+
replayed: boolean;
|
|
85
|
+
}
|
|
86
|
+
export interface MultiplayerWorldCharacterTurn {
|
|
87
|
+
readonly requestId: string;
|
|
88
|
+
readonly operationId: string;
|
|
89
|
+
readonly speaker: MultiplayerServerPlayer;
|
|
90
|
+
readonly characterId: string;
|
|
91
|
+
readonly input: string;
|
|
92
|
+
readonly audience: {
|
|
93
|
+
readonly disclosure: "participants" | "world";
|
|
94
|
+
readonly players: readonly MultiplayerServerPlayer[];
|
|
95
|
+
readonly characterIds: readonly string[];
|
|
96
|
+
};
|
|
97
|
+
/** Durably admits the exact host-authorized request. No arguments can broaden it. */
|
|
98
|
+
send(): Promise<MultiplayerWorldCharacterAdmission>;
|
|
99
|
+
/** Explicitly rejects the request before admission. */
|
|
100
|
+
reject(code?: string): void;
|
|
101
|
+
}
|
|
27
102
|
type CallbackResult = unknown;
|
|
28
103
|
export interface MultiplayerServerCallbacks {
|
|
29
104
|
onStart?: (session: MultiplayerServerSession) => CallbackResult;
|
|
@@ -31,6 +106,9 @@ export interface MultiplayerServerCallbacks {
|
|
|
31
106
|
onPlayerLeave?: (session: MultiplayerServerSession, player: MultiplayerServerPlayer) => CallbackResult;
|
|
32
107
|
/** Receives binary messages as Uint8Array values and JSON messages otherwise. */
|
|
33
108
|
onMessage?: (session: MultiplayerServerSession, player: MultiplayerServerPlayer, data: unknown) => CallbackResult;
|
|
109
|
+
onWorldCharacterTurn?: (session: MultiplayerServerSession, turn: MultiplayerWorldCharacterTurn) => CallbackResult;
|
|
110
|
+
onWorldCharacterCommand?: (session: MultiplayerServerSession, request: MultiplayerWorldCharacterCommandRequest) => CallbackResult;
|
|
111
|
+
onWorldCharacterRead?: (session: MultiplayerServerSession, request: MultiplayerWorldCharacterReadRequest) => CallbackResult;
|
|
34
112
|
onTick?: (session: MultiplayerServerSession) => CallbackResult;
|
|
35
113
|
onReplaced?: (session: MultiplayerServerSession) => CallbackResult;
|
|
36
114
|
/**
|