@antha/multiplayer-p2p-lock-step 0.0.8 → 0.1.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.
@@ -133,6 +133,7 @@ export declare class P2pLockStepMultiplayerController<MultiplayerPacket extends
133
133
  protected timeoutId: ReturnType<typeof globalThis.setTimeout> | undefined;
134
134
  protected frameTickReady: boolean;
135
135
  protected frameMs: number | undefined;
136
+ protected joiningRoom: boolean;
136
137
  protected lastFpsCalculation: {
137
138
  timestamp: number;
138
139
  frameCount: number;
@@ -186,9 +187,9 @@ export declare class P2pLockStepMultiplayerController<MultiplayerPacket extends
186
187
  * - For non-host clients, this includes the member client and the host client once connected.
187
188
  */
188
189
  getAllClientIds(): ClientId[];
189
- /** Start multiplayer mode. This delegates API connectivity and room polling to multiplayer core. */
190
+ /** Initialize multiplayer API access without opening a room or starting host pings. */
190
191
  initMultiplayer(params: Readonly<MultiplayerInitParams>): Promise<void>;
191
- /** Start singleplayer mode. */
192
+ /** Start local play without contacting the multiplayer API. This can later open into a room. */
192
193
  startSingleplayer(): void;
193
194
  /**
194
195
  * Manually run the next frame.
@@ -219,6 +220,8 @@ export declare class P2pLockStepMultiplayerController<MultiplayerPacket extends
219
220
  protected listenToRoomController(): void;
220
221
  /** Attach an established room transport and publish initial frame readiness. */
221
222
  protected attachMultiplayerRoomConnection(roomConnection: Readonly<MultiplayerRoomConnection<P2pLockStepMessage<MultiplayerPacket>>>): void;
223
+ /** Restart frame production if this client is promoted after losing its previous host. */
224
+ protected handleNewHost(clientId: ClientId): void;
222
225
  /** Send an empty frame to a newly connected member so it can join the frame flow. */
223
226
  protected syncNewMember(clientId: ClientId): void;
224
227
  /** Apply received member actions on the host or received frames on member clients. */
@@ -53,6 +53,7 @@ export class P2pLockStepMultiplayerController extends ListenTarget {
53
53
  timeoutId;
54
54
  frameTickReady = true;
55
55
  frameMs;
56
+ joiningRoom = false;
56
57
  lastFpsCalculation = {
57
58
  timestamp: 0,
58
59
  frameCount: 0,
@@ -68,6 +69,7 @@ export class P2pLockStepMultiplayerController extends ListenTarget {
68
69
  }).milliseconds;
69
70
  this.roomController = new MultiplayerRoomController({
70
71
  gameId: params.gameId,
72
+ clientId: this.localClientId,
71
73
  acceptConnection: params.acceptConnection
72
74
  ? (connectingClientId) => {
73
75
  this.debugLog(`checking incoming connection from ${connectingClientId}`);
@@ -143,13 +145,13 @@ export class P2pLockStepMultiplayerController extends ListenTarget {
143
145
  }
144
146
  return this.roomConnection?.getAllClientIds() || [];
145
147
  }
146
- /** Start multiplayer mode. This delegates API connectivity and room polling to multiplayer core. */
148
+ /** Initialize multiplayer API access without opening a room or starting host pings. */
147
149
  async initMultiplayer(params) {
148
150
  this.debugLog(`initializing multiplayer with backend ${params.backendOrigin}`);
149
151
  await this.roomController.initMultiplayer(params);
150
152
  this.debugLog('multiplayer API initialized');
151
153
  }
152
- /** Start singleplayer mode. */
154
+ /** Start local play without contacting the multiplayer API. This can later open into a room. */
153
155
  startSingleplayer() {
154
156
  if (this.currentConnection) {
155
157
  throw new Error('Cannot start singleplayer with a connection already present.');
@@ -223,23 +225,32 @@ export class P2pLockStepMultiplayerController extends ListenTarget {
223
225
  }
224
226
  /** Join or create a room. */
225
227
  async joinOrCreateRoom(room) {
226
- if (this.singleplayer) {
227
- throw new Error('Cannot join room: connection already established.');
228
- }
229
228
  const previousRoomConnection = this.roomConnection;
229
+ const wasSingleplayer = this.singleplayer;
230
230
  this.debugLog(`joining or creating room '${room.roomName}' (${room.roomId})`);
231
- const roomConnection = await this.joinRoom({
232
- previousRoomConnection,
233
- room,
234
- });
235
- if (previousRoomConnection) {
236
- globalThis.clearTimeout(this.timeoutId);
237
- this.clientsResponded = {};
238
- this.frameActions = [];
239
- this.frameTickReady = true;
231
+ this.joiningRoom = true;
232
+ try {
233
+ const roomConnection = await this.joinRoom({
234
+ previousRoomConnection,
235
+ room,
236
+ });
237
+ if (previousRoomConnection) {
238
+ globalThis.clearTimeout(this.timeoutId);
239
+ this.clientsResponded = {};
240
+ this.frameActions = [];
241
+ this.frameTickReady = true;
242
+ }
243
+ else if (wasSingleplayer) {
244
+ globalThis.clearTimeout(this.timeoutId);
245
+ this.frameTickReady = true;
246
+ }
247
+ this.singleplayer = false;
248
+ this.attachMultiplayerRoomConnection(roomConnection);
249
+ this.debugLog(`attached p2p-lock-step connection; client=${this.getClientId() || 'unknown'} host=${this.isHost()} connected=${this.isConnected()}`);
250
+ }
251
+ finally {
252
+ this.joiningRoom = false;
240
253
  }
241
- this.attachMultiplayerRoomConnection(roomConnection);
242
- this.debugLog(`attached p2p-lock-step connection; client=${this.getClientId() || 'unknown'} host=${this.isHost()} connected=${this.isConnected()}`);
243
254
  }
244
255
  /** Join through the core room controller while preserving the wrapper connection on failure. */
245
256
  async joinRoom({ previousRoomConnection, room, }) {
@@ -283,6 +294,9 @@ export class P2pLockStepMultiplayerController extends ListenTarget {
283
294
  if ('newMember' in event.detail) {
284
295
  this.syncNewMember(event.detail.newMember);
285
296
  }
297
+ else if ('newHost' in event.detail) {
298
+ this.handleNewHost(event.detail.newHost);
299
+ }
286
300
  this.dispatch(event);
287
301
  });
288
302
  this.roomController.listen((ControllerMessageEvent), (event) => {
@@ -306,6 +320,19 @@ export class P2pLockStepMultiplayerController extends ListenTarget {
306
320
  });
307
321
  }
308
322
  }
323
+ /** Restart frame production if this client is promoted after losing its previous host. */
324
+ handleNewHost(clientId) {
325
+ if (this.joiningRoom ||
326
+ clientId !== this.localClientId ||
327
+ !this.roomConnection ||
328
+ !this.roomConnection.isHost()) {
329
+ return;
330
+ }
331
+ globalThis.clearTimeout(this.timeoutId);
332
+ this.clientsResponded = {};
333
+ this.frameTickReady = true;
334
+ this.finishFrame();
335
+ }
309
336
  /** Send an empty frame to a newly connected member so it can join the frame flow. */
310
337
  syncNewMember(clientId) {
311
338
  this.debugLog(`syncNewMember called for ${clientId}; host=${this.isHost()}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antha/multiplayer-p2p-lock-step",
3
- "version": "0.0.8",
3
+ "version": "0.1.0",
4
4
  "description": "Multiplayer mod for the Antha engine.",
5
5
  "keywords": [
6
6
  "vir",
@@ -36,20 +36,20 @@
36
36
  "test:docs": "virmator docs check"
37
37
  },
38
38
  "dependencies": {
39
- "@augment-vir/assert": "^31.73.1",
40
- "@augment-vir/common": "^31.73.1",
41
- "date-vir": "^8.5.0",
42
- "typed-event-target": "^4.3.1"
39
+ "@augment-vir/assert": "^32.2.2",
40
+ "@augment-vir/common": "^32.2.2",
41
+ "date-vir": "^9.0.0",
42
+ "typed-event-target": "^4.3.3"
43
43
  },
44
44
  "devDependencies": {
45
- "@augment-vir/test": "^31.73.1",
46
- "@web/dev-server-esbuild": "^1.0.5",
47
- "@web/test-runner": "^0.20.2",
48
- "@web/test-runner-playwright": "^0.11.1",
45
+ "@augment-vir/test": "^32.2.2",
46
+ "@web/dev-server-esbuild": "^2.0.0",
47
+ "@web/test-runner": "^1.0.0",
48
+ "@web/test-runner-playwright": "^1.0.0",
49
49
  "istanbul-smart-text-reporter": "^1.1.5"
50
50
  },
51
51
  "peerDependencies": {
52
- "@antha/multiplayer-core": "^0.0.8"
52
+ "@antha/multiplayer-core": "^0.1.0"
53
53
  },
54
54
  "engines": {
55
55
  "node": ">=22"