@rpgjs/client 5.0.0-beta.15 → 5.0.0-beta.17

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 CHANGED
@@ -1,5 +1,25 @@
1
1
  # @rpgjs/client
2
2
 
3
+ ## 5.0.0-beta.17
4
+
5
+ ### Patch Changes
6
+
7
+ - Release the next RPGJS beta while keeping the physics package on its stable release line.
8
+ - Updated dependencies
9
+ - @rpgjs/common@5.0.0-beta.16
10
+ - @rpgjs/server@5.0.0-beta.17
11
+ - @rpgjs/ui-css@5.0.0-beta.14
12
+
13
+ ## 5.0.0-beta.16
14
+
15
+ ### Patch Changes
16
+
17
+ - Release the next RPGJS beta with terrain rendering performance improvements and a unified server tick loop.
18
+ - Updated dependencies
19
+ - @rpgjs/common@5.0.0-beta.15
20
+ - @rpgjs/server@5.0.0-beta.16
21
+ - @rpgjs/ui-css@5.0.0-beta.13
22
+
3
23
  ## 5.0.0-beta.15
4
24
 
5
25
  ### Patch Changes
@@ -66,6 +66,9 @@ export declare class RpgClientEngine<T = any> {
66
66
  private predictionEnabled;
67
67
  private prediction?;
68
68
  private readonly SERVER_CORRECTION_THRESHOLD;
69
+ private localMovementAuthority;
70
+ private lastLocalMovementInputAt;
71
+ private readonly LOCAL_MOVEMENT_AUTHORITY_ACK_GRACE_MS;
69
72
  private inputFrameCounter;
70
73
  private pendingPredictionFrames;
71
74
  private lastClientPhysicsStepAt;
@@ -103,6 +106,7 @@ export declare class RpgClientEngine<T = any> {
103
106
  private i18nService;
104
107
  private locale?;
105
108
  constructor(context: any);
109
+ private resolveLocalMovementAuthority;
106
110
  setLocale(locale: string): void;
107
111
  getLocale(): string;
108
112
  t(key: string, params?: I18nParams): string;
@@ -145,11 +149,17 @@ export declare class RpgClientEngine<T = any> {
145
149
  */
146
150
  setKeyboardControls(controlInstance: any): void;
147
151
  start(): Promise<void>;
152
+ private installCanvasResizeGuard;
153
+ private readCanvasResizeTargetSize;
154
+ private readCanvasRendererSize;
155
+ private cancelCanvasResizeFrame;
148
156
  private resolveSceneMapComponent;
149
157
  private setupPointerTracking;
150
158
  updatePointerFromInteractionEvent(event: any): void;
151
159
  private findViewportInstance;
152
160
  private prepareSyncPayload;
161
+ private shouldPreserveLocalPlayerPosition;
162
+ private shouldKeepLocalPlayerMovement;
153
163
  private normalizeAckWithSyncState;
154
164
  private initListeners;
155
165
  private beginMapTransfer;
@@ -111,6 +111,9 @@ var RpgClientEngine = class {
111
111
  this.gamePause = signal(false);
112
112
  this.predictionEnabled = false;
113
113
  this.SERVER_CORRECTION_THRESHOLD = 30;
114
+ this.localMovementAuthority = false;
115
+ this.lastLocalMovementInputAt = 0;
116
+ this.LOCAL_MOVEMENT_AUTHORITY_ACK_GRACE_MS = 250;
114
117
  this.inputFrameCounter = 0;
115
118
  this.pendingPredictionFrames = [];
116
119
  this.lastClientPhysicsStepAt = 0;
@@ -164,8 +167,16 @@ var RpgClientEngine = class {
164
167
  this.registerSpriteComponent("rpg:shape", __ce_component$4);
165
168
  this.registerSpriteComponent("rpg:image", __ce_component$5);
166
169
  this.predictionEnabled = this.globalConfig?.prediction?.enabled !== false;
170
+ this.localMovementAuthority = this.resolveLocalMovementAuthority();
167
171
  this.initializePredictionController();
168
172
  }
173
+ resolveLocalMovementAuthority() {
174
+ const predictionConfig = this.globalConfig?.prediction;
175
+ const configured = this.globalConfig?.movementAuthority ?? predictionConfig?.movementAuthority ?? predictionConfig?.authority ?? predictionConfig?.mode;
176
+ if (configured === "server" || configured === "network" || configured === false) return false;
177
+ if (configured === "client" || configured === "local" || configured === true) return true;
178
+ return this.webSocket.mode === "standalone";
179
+ }
169
180
  setLocale(locale) {
170
181
  this.locale = locale;
171
182
  }
@@ -240,6 +251,7 @@ var RpgClientEngine = class {
240
251
  this.selector = document.body.querySelector("#rpg");
241
252
  const bootstrapOptions = this.globalConfig?.bootstrapCanvasOptions;
242
253
  const { app, canvasElement } = await bootstrapCanvas(this.selector, __ce_component, bootstrapOptions);
254
+ this.installCanvasResizeGuard(app);
243
255
  this.canvasApp = app;
244
256
  this.canvasElement = canvasElement;
245
257
  this.renderer = app.renderer;
@@ -289,6 +301,45 @@ var RpgClientEngine = class {
289
301
  this.tickSubscriptions.push(tickSubscription);
290
302
  this.startPingPong();
291
303
  }
304
+ installCanvasResizeGuard(app) {
305
+ if (!app || typeof app.resize !== "function") return;
306
+ const originalResize = app.resize.bind(app);
307
+ app.resize = () => {
308
+ const targetSize = this.readCanvasResizeTargetSize(app);
309
+ const rendererSize = this.readCanvasRendererSize(app);
310
+ if (targetSize && rendererSize && targetSize.width === rendererSize.width && targetSize.height === rendererSize.height) {
311
+ this.cancelCanvasResizeFrame(app);
312
+ return;
313
+ }
314
+ originalResize();
315
+ };
316
+ }
317
+ readCanvasResizeTargetSize(app) {
318
+ const resizeTarget = app?.resizeTo;
319
+ if (!resizeTarget || typeof window === "undefined") return null;
320
+ const rawWidth = resizeTarget === window ? window.innerWidth : resizeTarget.clientWidth;
321
+ const rawHeight = resizeTarget === window ? window.innerHeight : resizeTarget.clientHeight;
322
+ const width = Math.round(Number(rawWidth));
323
+ const height = Math.round(Number(rawHeight));
324
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width < 0 || height < 0) return null;
325
+ return {
326
+ width,
327
+ height
328
+ };
329
+ }
330
+ readCanvasRendererSize(app) {
331
+ const screen = app?.renderer?.screen;
332
+ const width = Math.round(Number(screen?.width));
333
+ const height = Math.round(Number(screen?.height));
334
+ if (!Number.isFinite(width) || !Number.isFinite(height)) return null;
335
+ return {
336
+ width,
337
+ height
338
+ };
339
+ }
340
+ cancelCanvasResizeFrame(app) {
341
+ if (typeof app?._cancelResize === "function") app._cancelResize();
342
+ }
292
343
  resolveSceneMapComponent() {
293
344
  const components = this.hooks.getHookFunctions("client-sceneMap-component");
294
345
  const component = components[components.length - 1];
@@ -382,7 +433,8 @@ var RpgClientEngine = class {
382
433
  delete payload.timestamp;
383
434
  const myId = this.playerIdSignal();
384
435
  const players = payload.players;
385
- if (this.predictionEnabled && !!this.prediction?.hasPendingInputs() && myId && players && players[myId]) {
436
+ const localPatch = myId && players ? players[myId] : void 0;
437
+ if (this.shouldPreserveLocalPlayerPosition(localPatch) && myId && players && players[myId]) {
386
438
  const localPatch = { ...players[myId] };
387
439
  delete localPatch.x;
388
440
  delete localPatch.y;
@@ -395,6 +447,18 @@ var RpgClientEngine = class {
395
447
  }
396
448
  return payload;
397
449
  }
450
+ shouldPreserveLocalPlayerPosition(localPatch) {
451
+ if (!localPatch) return false;
452
+ if (this.predictionEnabled && !!this.prediction?.hasPendingInputs()) return true;
453
+ return this.shouldKeepLocalPlayerMovement();
454
+ }
455
+ shouldKeepLocalPlayerMovement() {
456
+ if (!this.localMovementAuthority || this.mapTransitionInProgress) return false;
457
+ const myId = this.playerIdSignal();
458
+ if (!(myId ? this.sceneMap?.players?.()?.[myId] : void 0)) return false;
459
+ if (this.prediction?.hasPendingInputs()) return true;
460
+ return Date.now() - this.lastLocalMovementInputAt <= this.LOCAL_MOVEMENT_AUTHORITY_ACK_GRACE_MS;
461
+ }
398
462
  normalizeAckWithSyncState(ack, syncData) {
399
463
  const myId = this.playerIdSignal();
400
464
  if (!myId) return ack;
@@ -1321,6 +1385,7 @@ var RpgClientEngine = class {
1321
1385
  if (timestamp < this.dashLockedUntil) return;
1322
1386
  this.dashLockedUntil = timestamp + cooldown;
1323
1387
  }
1388
+ this.lastLocalMovementInputAt = timestamp;
1324
1389
  let frame;
1325
1390
  let tick;
1326
1391
  if (this.predictionEnabled && this.prediction) {
@@ -1693,11 +1758,12 @@ var RpgClientEngine = class {
1693
1758
  }
1694
1759
  applyServerAck(ack) {
1695
1760
  this.updateServerTickEstimate(ack.serverTick);
1761
+ const keepLocalMovement = this.shouldKeepLocalPlayerMovement();
1696
1762
  if (this.predictionEnabled && this.prediction) {
1697
1763
  const result = this.prediction.applyServerAck({
1698
1764
  frame: ack.frame,
1699
1765
  serverTick: ack.serverTick,
1700
- state: typeof ack.x === "number" && typeof ack.y === "number" ? {
1766
+ state: !keepLocalMovement && typeof ack.x === "number" && typeof ack.y === "number" ? {
1701
1767
  x: ack.x,
1702
1768
  y: ack.y,
1703
1769
  direction: ack.direction
@@ -1707,6 +1773,7 @@ var RpgClientEngine = class {
1707
1773
  return;
1708
1774
  }
1709
1775
  if (typeof ack.x !== "number" || typeof ack.y !== "number") return;
1776
+ if (keepLocalMovement) return;
1710
1777
  const player = this.getCurrentPlayer();
1711
1778
  const myId = this.playerIdSignal();
1712
1779
  if (!player || !myId) return;