@antha/multiplayer-p2p-lock-step 0.16.0 → 0.18.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.
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type
|
|
3
|
-
import {
|
|
1
|
+
import { type ModExecuteParams } from '@antha/engine';
|
|
2
|
+
import { type ApiAndRoomConnectionState, MultiplayerControllerClientStatusEvent } from '@antha/multiplayer-core';
|
|
3
|
+
import { type JsonCompatibleValue, type MaybePromise, type PartialWithUndefined, type SelectFrom } from '@augment-vir/common';
|
|
4
|
+
import { MultiplayerControllerFrameEvent, type MultiplayerFramePacket, P2pLockStepMultiplayerController, type P2pLockStepMultiplayerControllerParams } from './p2p-lock-step-multiplayer-controller.js';
|
|
4
5
|
/**
|
|
5
6
|
* Engine state added by the p2p-lock-step multiplayer mod.
|
|
6
7
|
*
|
|
@@ -9,6 +10,8 @@ import { P2pLockStepMultiplayerController, type P2pLockStepMultiplayerController
|
|
|
9
10
|
export type AnthaMultiplayerP2pLockStepState<MultiplayerPacket extends JsonCompatibleValue = any> = {
|
|
10
11
|
/** Enables verbose multiplayer debug logs. */
|
|
11
12
|
debugMultiplayer?: boolean | undefined;
|
|
13
|
+
/** Number of completed lock-step frame updates. */
|
|
14
|
+
multiplayerLockstepTick: number;
|
|
12
15
|
/** P2p-lock-step controller state. */
|
|
13
16
|
multiplayerP2pLockStep: {
|
|
14
17
|
/** Multiplayer controller used to drive singleplayer or multiplayer frame sync. */
|
|
@@ -17,19 +20,47 @@ export type AnthaMultiplayerP2pLockStepState<MultiplayerPacket extends JsonCompa
|
|
|
17
20
|
connectionState: ApiAndRoomConnectionState;
|
|
18
21
|
};
|
|
19
22
|
};
|
|
23
|
+
/**
|
|
24
|
+
* Indicates whether a p2p-lock-step multiplayer room is currently connected.
|
|
25
|
+
*
|
|
26
|
+
* @category Util
|
|
27
|
+
*/
|
|
28
|
+
export declare function isMultiplayerRoomConnected({ multiplayerP2pLockStep, }: Readonly<Partial<SelectFrom<AnthaMultiplayerP2pLockStepState, {
|
|
29
|
+
multiplayerP2pLockStep: true;
|
|
30
|
+
}>>>): boolean;
|
|
20
31
|
/**
|
|
21
32
|
* Options for {@link createAnthaMultiplayerP2pLockStepMod}.
|
|
22
33
|
*
|
|
23
34
|
* @category Internal
|
|
24
35
|
*/
|
|
25
|
-
export type AnthaMultiplayerP2pLockStepOptions<MultiplayerPacket extends JsonCompatibleValue = any> = PartialWithUndefined<SelectFrom<P2pLockStepMultiplayerControllerParams<MultiplayerPacket>, {
|
|
36
|
+
export type AnthaMultiplayerP2pLockStepOptions<MultiplayerPacket extends JsonCompatibleValue = any, State extends AnthaMultiplayerP2pLockStepState<MultiplayerPacket> = AnthaMultiplayerP2pLockStepState<MultiplayerPacket>> = PartialWithUndefined<SelectFrom<P2pLockStepMultiplayerControllerParams<MultiplayerPacket>, {
|
|
26
37
|
acceptConnection: true;
|
|
27
38
|
debugMultiplayer: true;
|
|
39
|
+
frameDuration: true;
|
|
28
40
|
gameId: true;
|
|
29
|
-
}
|
|
41
|
+
}>> & PartialWithUndefined<{
|
|
42
|
+
/** Applies an individual action from within a frame event. */
|
|
43
|
+
handlePacket: (params: Readonly<{
|
|
44
|
+
packet: Readonly<MultiplayerFramePacket<MultiplayerPacket>>;
|
|
45
|
+
multiplayerController: P2pLockStepMultiplayerController<MultiplayerPacket>;
|
|
46
|
+
state: Partial<State>;
|
|
47
|
+
}>) => MaybePromise<void>;
|
|
48
|
+
/** Called after all actions in a frame have been applied, in singleplayer and multiplayer. */
|
|
49
|
+
runFrameUpdate: (params: Readonly<{
|
|
50
|
+
multiplayerFrameEvent: MultiplayerControllerFrameEvent<MultiplayerPacket>;
|
|
51
|
+
} & ModExecuteParams<State>>) => MaybePromise<void>;
|
|
52
|
+
/** Handles a client or host status change. */
|
|
53
|
+
handleClientStatus: (params: Readonly<{
|
|
54
|
+
event: Readonly<MultiplayerControllerClientStatusEvent>;
|
|
55
|
+
multiplayerController: P2pLockStepMultiplayerController<MultiplayerPacket>;
|
|
56
|
+
state: Partial<State>;
|
|
57
|
+
}>) => MaybePromise<void>;
|
|
58
|
+
}>;
|
|
30
59
|
/**
|
|
31
|
-
* Create the engine mod that owns p2p-lock-step multiplayer state.
|
|
60
|
+
* Create the engine mod that owns p2p-lock-step multiplayer state. When `handlePacket` and
|
|
61
|
+
* `runFrameUpdate` are provided, the mod also applies every lock-step frame serially, preserving
|
|
62
|
+
* every frame when the engine batches multiple frame events into one tick.
|
|
32
63
|
*
|
|
33
64
|
* @category Main
|
|
34
65
|
*/
|
|
35
|
-
export declare function createAnthaMultiplayerP2pLockStepMod<const MultiplayerPacket extends JsonCompatibleValue = any>(options?: Readonly<AnthaMultiplayerP2pLockStepOptions<MultiplayerPacket
|
|
66
|
+
export declare function createAnthaMultiplayerP2pLockStepMod<const MultiplayerPacket extends JsonCompatibleValue = any, State extends AnthaMultiplayerP2pLockStepState<MultiplayerPacket> = AnthaMultiplayerP2pLockStepState<MultiplayerPacket>>(options?: Readonly<AnthaMultiplayerP2pLockStepOptions<MultiplayerPacket, NoInfer<State>>>): import("@antha/engine").AnthaMod<NoInfer<State>>;
|
|
@@ -1,19 +1,48 @@
|
|
|
1
|
-
import { defineAnthaMod } from '@antha/engine';
|
|
2
|
-
import { emptyApiAndRoomConnectionState, MultiplayerControllerConnectionEvent, } from '@antha/multiplayer-core';
|
|
3
|
-
import { log, } from '@augment-vir/common';
|
|
4
|
-
import { P2pLockStepMultiplayerController, } from './p2p-lock-step-multiplayer-controller.js';
|
|
1
|
+
import { defineAnthaMod, ModExecutionTriggerType } from '@antha/engine';
|
|
2
|
+
import { emptyApiAndRoomConnectionState, MultiplayerControllerClientStatusEvent, MultiplayerControllerConnectionEvent, } from '@antha/multiplayer-core';
|
|
3
|
+
import { awaitedBlockingMap, log, } from '@augment-vir/common';
|
|
4
|
+
import { MultiplayerControllerFrameEvent, P2pLockStepMultiplayerController, } from './p2p-lock-step-multiplayer-controller.js';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Indicates whether a p2p-lock-step multiplayer room is currently connected.
|
|
7
|
+
*
|
|
8
|
+
* @category Util
|
|
9
|
+
*/
|
|
10
|
+
export function isMultiplayerRoomConnected({ multiplayerP2pLockStep, }) {
|
|
11
|
+
return !!multiplayerP2pLockStep?.multiplayerController.roomId;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create the engine mod that owns p2p-lock-step multiplayer state. When `handlePacket` and
|
|
15
|
+
* `runFrameUpdate` are provided, the mod also applies every lock-step frame serially, preserving
|
|
16
|
+
* every frame when the engine batches multiple frame events into one tick.
|
|
7
17
|
*
|
|
8
18
|
* @category Main
|
|
9
19
|
*/
|
|
10
20
|
export function createAnthaMultiplayerP2pLockStepMod(options = {}) {
|
|
11
21
|
return defineAnthaMod({
|
|
12
22
|
modName: 'antha-multiplayer-p2p-lock-step',
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
initState: {
|
|
24
|
+
debugMultiplayer: options.debugMultiplayer,
|
|
25
|
+
multiplayerLockstepTick: 0,
|
|
26
|
+
},
|
|
27
|
+
trigger: options.handlePacket || options.runFrameUpdate || options.handleClientStatus
|
|
28
|
+
? {
|
|
29
|
+
event: [
|
|
30
|
+
...(options.handlePacket || options.runFrameUpdate
|
|
31
|
+
? [MultiplayerControllerFrameEvent]
|
|
32
|
+
: []),
|
|
33
|
+
...(options.handleClientStatus
|
|
34
|
+
? [MultiplayerControllerClientStatusEvent]
|
|
35
|
+
: []),
|
|
36
|
+
],
|
|
37
|
+
executeImmediately: true,
|
|
16
38
|
}
|
|
39
|
+
: undefined,
|
|
40
|
+
cleanup({ state }) {
|
|
41
|
+
log.if(!!state.debugMultiplayer).faint('[multiplayer] cleaning up p2p-lock-step mod');
|
|
42
|
+
state.multiplayerP2pLockStep?.multiplayerController.destroy();
|
|
43
|
+
},
|
|
44
|
+
async execute(executeParams) {
|
|
45
|
+
const { engine, state } = executeParams;
|
|
17
46
|
if (!state.multiplayerP2pLockStep) {
|
|
18
47
|
log.if(!!state.debugMultiplayer).faint('[multiplayer] creating p2p-lock-step mod state');
|
|
19
48
|
state.multiplayerP2pLockStep = {
|
|
@@ -21,7 +50,7 @@ export function createAnthaMultiplayerP2pLockStepMod(options = {}) {
|
|
|
21
50
|
gameId: options.gameId || 'antha',
|
|
22
51
|
acceptConnection: options.acceptConnection,
|
|
23
52
|
debugMultiplayer: state.debugMultiplayer,
|
|
24
|
-
frameDuration:
|
|
53
|
+
frameDuration: options.frameDuration,
|
|
25
54
|
}),
|
|
26
55
|
connectionState: emptyApiAndRoomConnectionState,
|
|
27
56
|
};
|
|
@@ -32,11 +61,58 @@ export function createAnthaMultiplayerP2pLockStepMod(options = {}) {
|
|
|
32
61
|
log.if(!!state.debugMultiplayer).faint(`[multiplayer] mod connection state updated: api=${String(newConnectionState.api)} room=${String(newConnectionState.room)}`);
|
|
33
62
|
state.multiplayerP2pLockStep.connectionState = newConnectionState;
|
|
34
63
|
});
|
|
64
|
+
state.multiplayerP2pLockStep.multiplayerController.listenToAll((event) => {
|
|
65
|
+
engine.dispatch(event);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (executeParams.executionTrigger.type === ModExecutionTriggerType.Event) {
|
|
69
|
+
await awaitedBlockingMap(executeParams.executionTrigger.events, async (event) => {
|
|
70
|
+
if (!state.multiplayerP2pLockStep?.multiplayerController) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
else if (event instanceof MultiplayerControllerClientStatusEvent) {
|
|
74
|
+
await options.handleClientStatus?.({
|
|
75
|
+
event,
|
|
76
|
+
multiplayerController: state.multiplayerP2pLockStep.multiplayerController,
|
|
77
|
+
state,
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
else if (event instanceof MultiplayerControllerFrameEvent &&
|
|
82
|
+
(options.handlePacket || options.runFrameUpdate)) {
|
|
83
|
+
if (options.handlePacket) {
|
|
84
|
+
for (const detail of event.detail) {
|
|
85
|
+
await options.handlePacket({
|
|
86
|
+
packet: detail,
|
|
87
|
+
multiplayerController: state.multiplayerP2pLockStep.multiplayerController,
|
|
88
|
+
state,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
state.multiplayerLockstepTick = (state.multiplayerLockstepTick || 0) + 1;
|
|
93
|
+
await options.runFrameUpdate?.({
|
|
94
|
+
...executeParams,
|
|
95
|
+
currentTick: state.multiplayerLockstepTick,
|
|
96
|
+
executionTrigger: {
|
|
97
|
+
events: [
|
|
98
|
+
event,
|
|
99
|
+
],
|
|
100
|
+
type: ModExecutionTriggerType.Event,
|
|
101
|
+
},
|
|
102
|
+
/**
|
|
103
|
+
* This is a fixed duration so that clients don't get out of sync with
|
|
104
|
+
* each other. If a client is ever executing multiple events per
|
|
105
|
+
* execution, that means they got behind somehow, so we should still
|
|
106
|
+
* keep this duration fixed so that they run the same calculations as
|
|
107
|
+
* everyone else while catching up.
|
|
108
|
+
*/
|
|
109
|
+
msSinceLastExecute: state.multiplayerP2pLockStep.multiplayerController.frameMs,
|
|
110
|
+
multiplayerFrameEvent: event,
|
|
111
|
+
ticksSinceLastExecute: 1,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
});
|
|
35
115
|
}
|
|
36
|
-
},
|
|
37
|
-
cleanup({ state }) {
|
|
38
|
-
log.if(!!state.debugMultiplayer).faint('[multiplayer] cleaning up p2p-lock-step mod');
|
|
39
|
-
state.multiplayerP2pLockStep?.multiplayerController.destroy();
|
|
40
116
|
},
|
|
41
117
|
});
|
|
42
118
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ApiAndRoomConnectionState, type ClientId,
|
|
1
|
+
import { type ApiAndRoomConnectionState, type ClientId, MultiplayerControllerClientStatusEvent, MultiplayerControllerConnectionEvent, MultiplayerControllerRoomListEvent, type MultiplayerControllerRoomListListener, type MultiplayerInitParams, type MultiplayerRoomConnection, MultiplayerRoomController, type RoomInput, RoomRejectionError } from '@antha/multiplayer-core';
|
|
2
2
|
import { type JsonCompatibleValue, type MaybePromise, type PartialWithUndefined } from '@augment-vir/common';
|
|
3
3
|
import { type AnyDuration } from 'date-vir';
|
|
4
4
|
import { ListenTarget, type RemoveListenerCallback, type TypedCustomEventInit } from 'typed-event-target';
|
|
@@ -16,7 +16,7 @@ export declare enum P2pLockStepMessageType {
|
|
|
16
16
|
*
|
|
17
17
|
* @category Internal
|
|
18
18
|
*/
|
|
19
|
-
export type
|
|
19
|
+
export type MultiplayerFramePacket<MultiplayerPacket extends JsonCompatibleValue> = {
|
|
20
20
|
packet: MultiplayerPacket;
|
|
21
21
|
sourceClientId: ClientId;
|
|
22
22
|
};
|
|
@@ -33,10 +33,13 @@ export type P2pLockStepMessage<MultiplayerPacket extends JsonCompatibleValue> =
|
|
|
33
33
|
actions: MultiplayerPacket[];
|
|
34
34
|
}
|
|
35
35
|
/** Sent from the host to clients when a frame is ready. */
|
|
36
|
-
| {
|
|
36
|
+
| ({
|
|
37
37
|
type: P2pLockStepMessageType.Frame;
|
|
38
|
-
|
|
39
|
-
}
|
|
38
|
+
packets: MultiplayerFramePacket<MultiplayerPacket>[];
|
|
39
|
+
} & PartialWithUndefined<{
|
|
40
|
+
/** Whether this frame is meant for syncing a new client. */
|
|
41
|
+
isSynchronizationFrame: boolean;
|
|
42
|
+
}>);
|
|
40
43
|
/**
|
|
41
44
|
* Constructor parameters for {@link P2pLockStepMultiplayerController}.
|
|
42
45
|
*
|
|
@@ -86,15 +89,15 @@ declare const MultiplayerControllerFrameEvent_base: (new (eventInitDict: {
|
|
|
86
89
|
* @category Events
|
|
87
90
|
*/
|
|
88
91
|
export declare class MultiplayerControllerFrameEvent<MultiplayerPacket extends JsonCompatibleValue> extends MultiplayerControllerFrameEvent_base {
|
|
89
|
-
detail: ReadonlyArray<
|
|
90
|
-
constructor(eventInitDict: TypedCustomEventInit<ReadonlyArray<
|
|
92
|
+
detail: ReadonlyArray<MultiplayerFramePacket<MultiplayerPacket>>;
|
|
93
|
+
constructor(eventInitDict: TypedCustomEventInit<ReadonlyArray<MultiplayerFramePacket<MultiplayerPacket>>>);
|
|
91
94
|
}
|
|
92
95
|
/**
|
|
93
96
|
* All events emitted by this controller.
|
|
94
97
|
*
|
|
95
98
|
* @category Internal
|
|
96
99
|
*/
|
|
97
|
-
export type AllP2pLockStepMultiplayerControllerEvents<MultiplayerPacket extends JsonCompatibleValue> = MultiplayerControllerFrameEvent<MultiplayerPacket> | MultiplayerControllerRoomListEvent |
|
|
100
|
+
export type AllP2pLockStepMultiplayerControllerEvents<MultiplayerPacket extends JsonCompatibleValue> = MultiplayerControllerFrameEvent<MultiplayerPacket> | MultiplayerControllerRoomListEvent | MultiplayerControllerClientStatusEvent | MultiplayerControllerConnectionEvent;
|
|
98
101
|
/**
|
|
99
102
|
* Listener callback for p2p-lock-step frame events.
|
|
100
103
|
*
|
|
@@ -129,10 +132,10 @@ export declare class P2pLockStepMultiplayerController<MultiplayerPacket extends
|
|
|
129
132
|
protected readonly localClientId: ClientId;
|
|
130
133
|
protected roomConnection: MultiplayerRoomConnection<P2pLockStepMessage<MultiplayerPacket>> | undefined;
|
|
131
134
|
protected clientsResponded: Record<ClientId, boolean>;
|
|
132
|
-
protected frameActions:
|
|
135
|
+
protected frameActions: MultiplayerFramePacket<MultiplayerPacket>[];
|
|
133
136
|
protected timeoutId: ReturnType<typeof globalThis.setTimeout> | undefined;
|
|
134
137
|
protected frameTickReady: boolean;
|
|
135
|
-
|
|
138
|
+
frameMs: number;
|
|
136
139
|
protected joiningRoom: boolean;
|
|
137
140
|
protected lastFpsCalculation: {
|
|
138
141
|
timestamp: number;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createMultiplayerId, emptyApiAndRoomConnectionState, MultiplayerConnectionState,
|
|
1
|
+
import { createMultiplayerId, emptyApiAndRoomConnectionState, MultiplayerConnectionState, MultiplayerControllerClientStatusEvent, MultiplayerControllerConnectionEvent, MultiplayerControllerMessageEvent, MultiplayerControllerRoomListEvent, MultiplayerRoomController, RoomRejectionError, } from '@antha/multiplayer-core';
|
|
2
2
|
import { ensureArray, log, makeWritable, } from '@augment-vir/common';
|
|
3
3
|
import { convertDuration } from 'date-vir';
|
|
4
4
|
import { defineTypedCustomEvent, ListenTarget, } from 'typed-event-target';
|
|
@@ -174,9 +174,6 @@ export class P2pLockStepMultiplayerController extends ListenTarget {
|
|
|
174
174
|
*/
|
|
175
175
|
runFrame(actions) {
|
|
176
176
|
this.debugLog(`runFrame called with ${actions?.length || 0} actions`);
|
|
177
|
-
if (this.frameMs != undefined) {
|
|
178
|
-
throw new Error('Cannot manually run frame when frameDuration has been set.');
|
|
179
|
-
}
|
|
180
177
|
if (actions) {
|
|
181
178
|
this.act(actions);
|
|
182
179
|
}
|
|
@@ -289,7 +286,7 @@ export class P2pLockStepMultiplayerController extends ListenTarget {
|
|
|
289
286
|
this.debugLog(`connection event received: api=${String(event.detail.api)} room=${String(event.detail.room)}`);
|
|
290
287
|
this.dispatch(event);
|
|
291
288
|
});
|
|
292
|
-
this.roomController.listen(
|
|
289
|
+
this.roomController.listen(MultiplayerControllerClientStatusEvent, (event) => {
|
|
293
290
|
this.debugLog(`client event received: ${JSON.stringify(event.detail)}`);
|
|
294
291
|
if ('newMember' in event.detail) {
|
|
295
292
|
this.syncNewMember(event.detail.newMember);
|
|
@@ -339,7 +336,8 @@ export class P2pLockStepMultiplayerController extends ListenTarget {
|
|
|
339
336
|
if (this.roomConnection && this.isHost()) {
|
|
340
337
|
this.roomConnection.sendToOnlyOneClient(clientId, {
|
|
341
338
|
type: P2pLockStepMessageType.Frame,
|
|
342
|
-
|
|
339
|
+
packets: [],
|
|
340
|
+
isSynchronizationFrame: true,
|
|
343
341
|
});
|
|
344
342
|
}
|
|
345
343
|
}
|
|
@@ -368,10 +366,9 @@ export class P2pLockStepMultiplayerController extends ListenTarget {
|
|
|
368
366
|
this.maybeFinishFrame();
|
|
369
367
|
}
|
|
370
368
|
else if (!this.isHost() && message.type === P2pLockStepMessageType.Frame) {
|
|
371
|
-
this.debugLog(`member received frame with ${message.
|
|
369
|
+
this.debugLog(`member received frame with ${message.packets.length} actions; sending ${this.frameActions.length} local actions back to host`);
|
|
372
370
|
const currentFrameActions = this.frameActions;
|
|
373
371
|
this.frameActions = [];
|
|
374
|
-
this.calculateFps();
|
|
375
372
|
this.roomConnection.sendMessage({
|
|
376
373
|
actions: currentFrameActions.map(({ packet }) => {
|
|
377
374
|
return packet;
|
|
@@ -379,9 +376,12 @@ export class P2pLockStepMultiplayerController extends ListenTarget {
|
|
|
379
376
|
sourceClientId: this.clientId,
|
|
380
377
|
type: P2pLockStepMessageType.Actions,
|
|
381
378
|
});
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
379
|
+
if (!message.isSynchronizationFrame) {
|
|
380
|
+
this.calculateFps();
|
|
381
|
+
this.dispatch(new MultiplayerControllerFrameEvent({
|
|
382
|
+
detail: message.packets,
|
|
383
|
+
}));
|
|
384
|
+
}
|
|
385
385
|
}
|
|
386
386
|
}
|
|
387
387
|
/** Recalculate the current data-flow FPS from completed frames. */
|
|
@@ -408,7 +408,7 @@ export class P2pLockStepMultiplayerController extends ListenTarget {
|
|
|
408
408
|
this.frameActions = [];
|
|
409
409
|
this.roomConnection?.sendMessage({
|
|
410
410
|
type: P2pLockStepMessageType.Frame,
|
|
411
|
-
|
|
411
|
+
packets: currentFrameActions,
|
|
412
412
|
});
|
|
413
413
|
this.dispatch(new MultiplayerControllerFrameEvent({
|
|
414
414
|
detail: currentFrameActions,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antha/multiplayer-p2p-lock-step",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Multiplayer mod for the Antha engine.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vir",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"typed-event-target": "^4.3.3"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
+
"@antha/multiplayer-core": "^0.18.0",
|
|
45
46
|
"@augment-vir/test": "^32.3.0",
|
|
46
47
|
"@web/dev-server-esbuild": "^2.0.0",
|
|
47
48
|
"@web/test-runner": "^1.0.0",
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
"istanbul-smart-text-reporter": "^1.1.5"
|
|
50
51
|
},
|
|
51
52
|
"peerDependencies": {
|
|
52
|
-
"@antha/multiplayer-core": "^0.
|
|
53
|
+
"@antha/multiplayer-core": "^0.18.0"
|
|
53
54
|
},
|
|
54
55
|
"engines": {
|
|
55
56
|
"node": ">=22"
|