@rpgjs/client 5.0.0-beta.14 → 5.0.0-beta.16

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,22 @@
1
1
  # @rpgjs/client
2
2
 
3
+ ## 5.0.0-beta.16
4
+
5
+ ### Patch Changes
6
+
7
+ - Release the next RPGJS beta with terrain rendering performance improvements and a unified server tick loop.
8
+ - Updated dependencies
9
+ - @rpgjs/common@5.0.0-beta.15
10
+ - @rpgjs/server@5.0.0-beta.16
11
+ - @rpgjs/ui-css@5.0.0-beta.13
12
+
13
+ ## 5.0.0-beta.15
14
+
15
+ ### Patch Changes
16
+
17
+ - dba133e: Queue early changeMap packets until the client has finished loading modules and GUI definitions.
18
+ - @rpgjs/server@5.0.0-beta.15
19
+
3
20
  ## 5.0.0-beta.14
4
21
 
5
22
  ### Patch Changes
package/dist/Game/Map.js CHANGED
@@ -8,6 +8,14 @@ import { RpgClientEngine } from "../RpgClientEngine.js";
8
8
  import { computed, signal } from "canvasengine";
9
9
  import { RpgCommonMap, cloneLightingState, normalizeLightingState } from "@rpgjs/common";
10
10
  //#region src/Game/Map.ts
11
+ var lightingColorsEqual = (left, right) => {
12
+ if (Array.isArray(left) || Array.isArray(right)) return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => value === right[index]);
13
+ return left === right;
14
+ };
15
+ var lightSpotsEqual = (left, right) => {
16
+ if (!left) return false;
17
+ return left.id === right.id && left.x === right.x && left.y === right.y && left.radius === right.radius && left.intensity === right.intensity && lightingColorsEqual(left.color, right.color) && left.flicker === right.flicker && left.flickerSpeed === right.flickerSpeed && left.pulse === right.pulse && left.pulseSpeed === right.pulseSpeed && left.phase === right.phase;
18
+ };
11
19
  var RpgClientMap = class extends RpgCommonMap {
12
20
  constructor() {
13
21
  super();
@@ -87,10 +95,13 @@ var RpgClientMap = class extends RpgCommonMap {
87
95
  id
88
96
  }] })?.spots?.[0];
89
97
  if (!nextSpot) return;
90
- this.localLightSpots.update((spots) => ({
91
- ...spots,
92
- [id]: nextSpot
93
- }));
98
+ this.localLightSpots.update((spots) => {
99
+ if (lightSpotsEqual(spots[id], nextSpot)) return spots;
100
+ return {
101
+ ...spots,
102
+ [id]: nextSpot
103
+ };
104
+ });
94
105
  }
95
106
  patchLightSpot(id, patch) {
96
107
  this.localLightSpots.update((spots) => {
@@ -1 +1 @@
1
- {"version":3,"file":"Map.js","names":[],"sources":["../../src/Game/Map.ts"],"sourcesContent":["import {\n RpgCommonMap,\n cloneLightingState,\n normalizeLightingState,\n type LightSpot,\n type LightingState,\n type WeatherState,\n type MapPhysicsInitContext,\n type MapPhysicsEntityContext,\n} from \"@rpgjs/common\";\nimport { sync, users } from \"@signe/sync\";\nimport { RpgClientPlayer } from \"./Player\";\nimport { Signal, signal, computed, effect } from \"canvasengine\";\nimport { RpgClientEvent } from \"./Event\";\nimport { RpgClientEngine } from \"../RpgClientEngine\";\nimport { inject } from \"../core/inject\";\n\ntype TestGlobalScope = typeof globalThis & {\n process?: {\n env?: {\n TEST?: string;\n };\n };\n __RPGJS_TEST__?: boolean;\n};\n\nexport class RpgClientMap extends RpgCommonMap<any> {\n engine: RpgClientEngine = inject(RpgClientEngine)\n @users(RpgClientPlayer) players = signal<Record<string, RpgClientPlayer>>({});\n @sync(RpgClientEvent) events = signal<Record<string, RpgClientEvent>>({});\n currentPlayer = computed(() => this.players()[this.engine.playerIdSignal()!])\n weatherState = signal<WeatherState | null>(null);\n localWeatherOverride = signal<WeatherState | null>(null);\n lightingState = signal<LightingState | null>(null);\n localLightSpots = signal<Record<string, LightSpot>>({});\n weather = computed<WeatherState | null>(() => {\n const local = this.localWeatherOverride() \n const state = this.weatherState()\n return local ?? state\n });\n lighting = computed<LightingState | null>(() => {\n const state = cloneLightingState(this.lightingState());\n const localSpots = Object.entries(this.localLightSpots()).map(([id, spot]) => ({\n ...spot,\n id: spot.id ?? id,\n }));\n\n if (!state && localSpots.length === 0) {\n return null;\n }\n\n const next: LightingState = { ...(state ?? {}) };\n if (!state?.ambient) {\n delete next.ambient;\n }\n next.spots = [\n ...(state?.spots ?? []),\n ...localSpots,\n ];\n return next;\n });\n private manualClientPhysicsTick = false;\n private readonly isTestEnvironment: boolean;\n\n constructor() {\n super();\n // Détecter l'environnement de test\n const testGlobal = globalThis as TestGlobalScope;\n const isTest = testGlobal.process?.env?.TEST === 'true'\n || testGlobal.__RPGJS_TEST__ === true;\n this.isTestEnvironment = isTest;\n if (isTest) {\n this.autoTickEnabled = false;\n }\n }\n\n configureClientPrediction(enabled: boolean): void {\n this.manualClientPhysicsTick = enabled;\n this.autoTickEnabled = enabled ? false : !this.isTestEnvironment;\n }\n\n getCurrentPlayer() {\n return this.currentPlayer()\n }\n\n reset(force = false) {\n const currentPlayerId = this.engine.playerIdSignal();\n const currentPlayer = !force && currentPlayerId\n ? this.players()[currentPlayerId]\n : undefined;\n const players = this.players();\n const events = this.events();\n\n Object.entries(players).forEach(([id, player]) => {\n if (!player || (!force && id === currentPlayerId)) return;\n (player as any).resetAnimationState?.();\n });\n Object.values(events).forEach((event) => {\n (event as any)?.resetAnimationState?.();\n });\n\n this.players.set(\n currentPlayerId && currentPlayer ? { [currentPlayerId]: currentPlayer } : {}\n );\n this.events.set({})\n this.weatherState.set(null);\n this.localWeatherOverride.set(null);\n this.lightingState.set(null);\n this.localLightSpots.set({});\n this.clearPhysic()\n }\n\n getWeather(): WeatherState | null {\n return this.weather();\n }\n\n setLocalWeather(next: WeatherState | null): void {\n this.localWeatherOverride.set(next);\n }\n\n clearLocalWeather(): void {\n this.localWeatherOverride.set(null);\n }\n\n getLighting(): LightingState | null {\n return this.lighting();\n }\n\n addLightSpot(id: string, spot: LightSpot): void {\n const normalized = normalizeLightingState({ spots: [{ ...spot, id }] });\n const nextSpot = normalized?.spots?.[0];\n if (!nextSpot) {\n return;\n }\n this.localLightSpots.update((spots) => ({\n ...spots,\n [id]: nextSpot,\n }));\n }\n\n patchLightSpot(id: string, patch: Partial<LightSpot>): void {\n this.localLightSpots.update((spots) => {\n const current = spots[id];\n if (!current) {\n return spots;\n }\n return {\n ...spots,\n [id]: {\n ...current,\n ...patch,\n id,\n x: patch.x ?? current.x,\n y: patch.y ?? current.y,\n },\n };\n });\n }\n\n removeLightSpot(id: string): void {\n this.localLightSpots.update((spots) => {\n if (!(id in spots)) {\n return spots;\n }\n const next = { ...spots };\n delete next[id];\n return next;\n });\n }\n\n clearLightSpots(): void {\n this.localLightSpots.set({});\n }\n\n stepClientPhysics(deltaMs: number): number {\n if (!this.manualClientPhysicsTick) {\n return 0;\n }\n return this.nextTick(deltaMs);\n }\n\n stepPredictionTick(): void {\n this.forceSingleTick();\n }\n\n protected emitPhysicsInit(context: MapPhysicsInitContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsInit\", this, context);\n }\n\n protected emitPhysicsEntityAdd(context: MapPhysicsEntityContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsEntityAdd\", this, context);\n }\n\n protected emitPhysicsEntityRemove(context: MapPhysicsEntityContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsEntityRemove\", this, context);\n }\n\n protected emitPhysicsReset(): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsReset\", this);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Map.js","names":[],"sources":["../../src/Game/Map.ts"],"sourcesContent":["import {\n RpgCommonMap,\n cloneLightingState,\n normalizeLightingState,\n type LightSpot,\n type LightingState,\n type WeatherState,\n type MapPhysicsInitContext,\n type MapPhysicsEntityContext,\n} from \"@rpgjs/common\";\nimport { sync, users } from \"@signe/sync\";\nimport { RpgClientPlayer } from \"./Player\";\nimport { Signal, signal, computed, effect } from \"canvasengine\";\nimport { RpgClientEvent } from \"./Event\";\nimport { RpgClientEngine } from \"../RpgClientEngine\";\nimport { inject } from \"../core/inject\";\n\ntype TestGlobalScope = typeof globalThis & {\n process?: {\n env?: {\n TEST?: string;\n };\n };\n __RPGJS_TEST__?: boolean;\n};\n\nconst lightingColorsEqual = (\n left: LightSpot[\"color\"],\n right: LightSpot[\"color\"],\n): boolean => {\n if (Array.isArray(left) || Array.isArray(right)) {\n return Array.isArray(left)\n && Array.isArray(right)\n && left.length === right.length\n && left.every((value, index) => value === right[index]);\n }\n return left === right;\n};\n\nconst lightSpotsEqual = (left: LightSpot | undefined, right: LightSpot): boolean => {\n if (!left) return false;\n return left.id === right.id\n && left.x === right.x\n && left.y === right.y\n && left.radius === right.radius\n && left.intensity === right.intensity\n && lightingColorsEqual(left.color, right.color)\n && left.flicker === right.flicker\n && left.flickerSpeed === right.flickerSpeed\n && left.pulse === right.pulse\n && left.pulseSpeed === right.pulseSpeed\n && left.phase === right.phase;\n};\n\nexport class RpgClientMap extends RpgCommonMap<any> {\n engine: RpgClientEngine = inject(RpgClientEngine)\n @users(RpgClientPlayer) players = signal<Record<string, RpgClientPlayer>>({});\n @sync(RpgClientEvent) events = signal<Record<string, RpgClientEvent>>({});\n currentPlayer = computed(() => this.players()[this.engine.playerIdSignal()!])\n weatherState = signal<WeatherState | null>(null);\n localWeatherOverride = signal<WeatherState | null>(null);\n lightingState = signal<LightingState | null>(null);\n localLightSpots = signal<Record<string, LightSpot>>({});\n weather = computed<WeatherState | null>(() => {\n const local = this.localWeatherOverride() \n const state = this.weatherState()\n return local ?? state\n });\n lighting = computed<LightingState | null>(() => {\n const state = cloneLightingState(this.lightingState());\n const localSpots = Object.entries(this.localLightSpots()).map(([id, spot]) => ({\n ...spot,\n id: spot.id ?? id,\n }));\n\n if (!state && localSpots.length === 0) {\n return null;\n }\n\n const next: LightingState = { ...(state ?? {}) };\n if (!state?.ambient) {\n delete next.ambient;\n }\n next.spots = [\n ...(state?.spots ?? []),\n ...localSpots,\n ];\n return next;\n });\n private manualClientPhysicsTick = false;\n private readonly isTestEnvironment: boolean;\n\n constructor() {\n super();\n // Détecter l'environnement de test\n const testGlobal = globalThis as TestGlobalScope;\n const isTest = testGlobal.process?.env?.TEST === 'true'\n || testGlobal.__RPGJS_TEST__ === true;\n this.isTestEnvironment = isTest;\n if (isTest) {\n this.autoTickEnabled = false;\n }\n }\n\n configureClientPrediction(enabled: boolean): void {\n this.manualClientPhysicsTick = enabled;\n this.autoTickEnabled = enabled ? false : !this.isTestEnvironment;\n }\n\n getCurrentPlayer() {\n return this.currentPlayer()\n }\n\n reset(force = false) {\n const currentPlayerId = this.engine.playerIdSignal();\n const currentPlayer = !force && currentPlayerId\n ? this.players()[currentPlayerId]\n : undefined;\n const players = this.players();\n const events = this.events();\n\n Object.entries(players).forEach(([id, player]) => {\n if (!player || (!force && id === currentPlayerId)) return;\n (player as any).resetAnimationState?.();\n });\n Object.values(events).forEach((event) => {\n (event as any)?.resetAnimationState?.();\n });\n\n this.players.set(\n currentPlayerId && currentPlayer ? { [currentPlayerId]: currentPlayer } : {}\n );\n this.events.set({})\n this.weatherState.set(null);\n this.localWeatherOverride.set(null);\n this.lightingState.set(null);\n this.localLightSpots.set({});\n this.clearPhysic()\n }\n\n getWeather(): WeatherState | null {\n return this.weather();\n }\n\n setLocalWeather(next: WeatherState | null): void {\n this.localWeatherOverride.set(next);\n }\n\n clearLocalWeather(): void {\n this.localWeatherOverride.set(null);\n }\n\n getLighting(): LightingState | null {\n return this.lighting();\n }\n\n addLightSpot(id: string, spot: LightSpot): void {\n const normalized = normalizeLightingState({ spots: [{ ...spot, id }] });\n const nextSpot = normalized?.spots?.[0];\n if (!nextSpot) {\n return;\n }\n this.localLightSpots.update((spots) => {\n if (lightSpotsEqual(spots[id], nextSpot)) {\n return spots;\n }\n return {\n ...spots,\n [id]: nextSpot,\n };\n });\n }\n\n patchLightSpot(id: string, patch: Partial<LightSpot>): void {\n this.localLightSpots.update((spots) => {\n const current = spots[id];\n if (!current) {\n return spots;\n }\n return {\n ...spots,\n [id]: {\n ...current,\n ...patch,\n id,\n x: patch.x ?? current.x,\n y: patch.y ?? current.y,\n },\n };\n });\n }\n\n removeLightSpot(id: string): void {\n this.localLightSpots.update((spots) => {\n if (!(id in spots)) {\n return spots;\n }\n const next = { ...spots };\n delete next[id];\n return next;\n });\n }\n\n clearLightSpots(): void {\n this.localLightSpots.set({});\n }\n\n stepClientPhysics(deltaMs: number): number {\n if (!this.manualClientPhysicsTick) {\n return 0;\n }\n return this.nextTick(deltaMs);\n }\n\n stepPredictionTick(): void {\n this.forceSingleTick();\n }\n\n protected emitPhysicsInit(context: MapPhysicsInitContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsInit\", this, context);\n }\n\n protected emitPhysicsEntityAdd(context: MapPhysicsEntityContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsEntityAdd\", this, context);\n }\n\n protected emitPhysicsEntityRemove(context: MapPhysicsEntityContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsEntityRemove\", this, context);\n }\n\n protected emitPhysicsReset(): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsReset\", this);\n }\n}\n"],"mappings":"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"}
@@ -27,6 +27,7 @@ var RpgClientObject = class extends RpgCommonPlayer {
27
27
  this.frames = [];
28
28
  this.graphicsSignals = signal([]);
29
29
  this.flashTrigger = trigger();
30
+ const engine = this.engine;
30
31
  this.hooks.callHooks("client-sprite-onInit", this).subscribe();
31
32
  this._frames.observable.subscribe(({ items }) => {
32
33
  if (!this.id) return;
@@ -36,16 +37,16 @@ var RpgClientObject = class extends RpgCommonPlayer {
36
37
  const graphicRefs = Array.isArray(graphics) ? graphics : [];
37
38
  if (graphicRefs.length === 0) return of([]);
38
39
  return from(Promise.all(graphicRefs.map(async (graphic) => {
39
- return withGraphicDisplayScale(await this.engine.getSpriteSheet(graphic), scale);
40
+ return withGraphicDisplayScale(await engine.getSpriteSheet(graphic), scale);
40
41
  })));
41
42
  })).subscribe((sheets) => {
42
43
  this.graphicsSignals.set(sheets);
43
44
  });
44
- this.engine.tick.pipe().subscribe(() => {
45
+ engine.tick.pipe().subscribe(() => {
45
46
  const frame = this.frames.shift();
46
47
  if (frame) {
47
48
  if (typeof frame.x !== "number" || typeof frame.y !== "number") return;
48
- this.engine.scene.setBodyPosition(this.id, frame.x, frame.y, "top-left");
49
+ engine.scene.setBodyPosition(this.id, frame.x, frame.y, "top-left");
49
50
  }
50
51
  });
51
52
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Object.js","names":[],"sources":["../../src/Game/Object.ts"],"sourcesContent":["import { Hooks, ModulesToken, RpgCommonPlayer } from \"@rpgjs/common\";\nimport { trigger, signal, type Trigger } from \"canvasengine\";\nimport { combineLatest, from, map, of, startWith, Subscription, switchMap } from \"rxjs\";\nimport { inject } from \"../core/inject\";\nimport { RpgClientEngine } from \"../RpgClientEngine\";\ntype Frame = { x: number; y: number; ts: number };\n\ntype AnimationRestoreOptions = {\n restoreAnimationName?: string;\n restoreGraphics?: any[];\n timeoutMs?: number;\n};\n\ntype FlashType = 'alpha' | 'tint' | 'both';\n\ntype FlashOptions = {\n type?: FlashType;\n duration?: number;\n cycles?: number;\n alpha?: number;\n tint?: number | string;\n};\n\ntype FlashTriggerOptions = Omit<FlashOptions, \"tint\"> & {\n tint: number;\n};\n\ntype ConfigurableTrigger<T> = Omit<Trigger<T>, \"start\"> & {\n start(config?: T): Promise<void>;\n};\n\nexport const withGraphicDisplayScale = (spritesheet: any, scale: unknown): any => {\n if (!spritesheet || typeof spritesheet !== \"object\") return spritesheet;\n if (scale === undefined || scale === null) return spritesheet;\n return {\n ...spritesheet,\n displayScale: scale,\n };\n};\n\nexport const appendFramePayload = (current: unknown, items: unknown): Frame[] => {\n const frameItems = Array.isArray(items) ? items : items ? [items] : [];\n const nextFrames = frameItems.flatMap((item): Frame[] =>\n Array.isArray(item) ? item : [item as Frame]\n );\n const currentFrames = Array.isArray(current) ? current as Frame[] : [];\n return currentFrames.concat(nextFrames);\n};\n\nexport abstract class RpgClientObject extends RpgCommonPlayer {\n abstract _type: string;\n emitParticleTrigger = trigger();\n particleName = signal(\"\");\n animationCurrentIndex = signal(0);\n animationIsPlaying = signal(false);\n _param = signal({});\n frames: Frame[] = [];\n graphicsSignals = signal<any[]>([]);\n flashTrigger: ConfigurableTrigger<FlashTriggerOptions> = trigger<FlashTriggerOptions>();\n private animationRestoreState?: {\n animationName: string;\n graphics: any[];\n };\n\n constructor() {\n super();\n this.hooks.callHooks(\"client-sprite-onInit\", this).subscribe();\n\n this._frames.observable.subscribe(({ items }) => {\n if (!this.id) return;\n //if (this.id == this.engine.playerIdSignal()!) return;\n this.frames = appendFramePayload(this.frames, items);\n });\n\n const graphics$ = this.graphics.observable.pipe(map(({ items }) => items));\n const graphicScale$ = this._graphicScale.observable.pipe(\n startWith({ value: this._graphicScale() }),\n map((payload: any) => payload?.value ?? payload),\n );\n\n combineLatest([graphics$, graphicScale$])\n .pipe(\n switchMap(([graphics, scale]) => {\n const graphicRefs = Array.isArray(graphics) ? graphics : [];\n if (graphicRefs.length === 0) return of([]);\n return from(Promise.all(graphicRefs.map(async (graphic) => {\n const spritesheet = await this.engine.getSpriteSheet(graphic);\n return withGraphicDisplayScale(spritesheet, scale);\n })));\n })\n )\n .subscribe((sheets) => { \n this.graphicsSignals.set(sheets);\n });\n\n this.engine.tick\n .pipe\n //throttleTime(10)\n ()\n .subscribe(() => {\n const frame = this.frames.shift();\n if (frame) {\n if (typeof frame.x !== \"number\" || typeof frame.y !== \"number\") return;\n this.engine.scene.setBodyPosition(\n this.id,\n frame.x,\n frame.y,\n \"top-left\"\n );\n }\n });\n }\n\n /**\n * Access the shared client hook registry.\n *\n * @returns The hook service used to register and trigger client-side hooks.\n */\n get hooks() {\n return inject<Hooks>(ModulesToken);\n }\n\n /**\n * Access the current client engine instance.\n *\n * @returns The active {@link RpgClientEngine} instance.\n */\n get engine() {\n return inject(RpgClientEngine);\n }\n\n private animationSubscription?: Subscription;\n private animationResetTimeout?: ReturnType<typeof setTimeout>;\n private animationWaitResolve?: () => void;\n\n private clearAnimationControls() {\n if (this.animationSubscription) {\n this.animationSubscription.unsubscribe();\n this.animationSubscription = undefined;\n }\n if (this.animationResetTimeout) {\n clearTimeout(this.animationResetTimeout);\n this.animationResetTimeout = undefined;\n }\n }\n\n private resolveAnimationWait() {\n const resolve = this.animationWaitResolve;\n this.animationWaitResolve = undefined;\n resolve?.();\n }\n\n private finishTemporaryAnimation() {\n const restoreState = this.animationRestoreState;\n this.clearAnimationControls();\n this.animationCurrentIndex.set(0);\n this.animationRestoreState = undefined;\n this.animationIsPlaying.set(false);\n if (restoreState) {\n this.animationName.set(restoreState.animationName);\n this.graphics.set([...restoreState.graphics]);\n }\n this.resolveAnimationWait();\n }\n\n /**\n * Trigger a flash animation on this sprite\n * \n * This method triggers a flash effect using CanvasEngine's flash directive.\n * The flash can be configured with various options including type (alpha, tint, or both),\n * duration, cycles, and color.\n * \n * ## Design\n * \n * The flash uses a trigger system that is connected to the flash directive in the\n * character component. This allows for flexible configuration and can be triggered\n * from both server events and client-side code.\n * \n * @param options - Flash configuration options\n * @param options.type - Type of flash effect: 'alpha' (opacity), 'tint' (color), or 'both' (default: 'alpha')\n * @param options.duration - Duration of the flash animation in milliseconds (default: 300)\n * @param options.cycles - Number of flash cycles (flash on/off) (default: 1)\n * @param options.alpha - Alpha value when flashing, from 0 to 1 (default: 0.3)\n * @param options.tint - Tint color when flashing as hex value or color name (default: 0xffffff - white)\n * \n * @example\n * ```ts\n * // Simple flash with default settings (alpha flash)\n * player.flash();\n * \n * // Flash with red tint\n * player.flash({ type: 'tint', tint: 0xff0000 });\n * \n * // Flash with both alpha and tint\n * player.flash({ \n * type: 'both', \n * alpha: 0.5, \n * tint: 0xff0000,\n * duration: 200,\n * cycles: 2\n * });\n * \n * // Quick damage flash\n * player.flash({ \n * type: 'tint', \n * tint: 0xff0000, \n * duration: 150,\n * cycles: 1\n * });\n * ```\n */\n flash(options?: FlashOptions): void {\n const flashOptions = {\n type: options?.type || 'alpha',\n duration: options?.duration ?? 300,\n cycles: options?.cycles ?? 1,\n alpha: options?.alpha ?? 0.3,\n tint: options?.tint ?? 0xffffff,\n };\n \n // Convert color name to hex if needed\n let tintValue = flashOptions.tint;\n if (typeof tintValue === 'string') {\n // Common color name to hex mapping\n const colorMap: Record<string, number> = {\n 'white': 0xffffff,\n 'red': 0xff0000,\n 'green': 0x00ff00,\n 'blue': 0x0000ff,\n 'yellow': 0xffff00,\n 'cyan': 0x00ffff,\n 'magenta': 0xff00ff,\n 'black': 0x000000,\n };\n tintValue = colorMap[tintValue.toLowerCase()] ?? 0xffffff;\n }\n \n this.flashTrigger.start({\n ...flashOptions,\n tint: tintValue,\n });\n }\n\n /**\n * Reset animation state when animation changes externally\n *\n * This method should be called when the animation changes due to movement\n * or other external factors to ensure the animation system doesn't get stuck\n *\n * @example\n * ```ts\n * // Reset when player starts moving\n * player.resetAnimationState();\n * ```\n */\n resetAnimationState() {\n if (this.animationRestoreState) {\n this.finishTemporaryAnimation();\n return;\n }\n this.animationIsPlaying.set(false);\n this.animationCurrentIndex.set(0);\n this.clearAnimationControls();\n this.resolveAnimationWait();\n }\n\n /**\n * Set a custom animation for a specific number of times\n *\n * Plays a custom animation for the specified number of repetitions.\n * The animation system prevents overlapping animations and automatically\n * returns to the previous animation when complete.\n *\n * @param animationName - Name of the animation to play\n * @param nbTimes - Number of times to repeat the animation (default: Infinity for continuous)\n * @param options - Restore and timeout options\n * @returns A promise resolved when a finite animation finishes, is interrupted, or times out\n *\n * @example\n * ```ts\n * // Play attack animation 3 times\n * await player.setAnimation('attack', 3);\n *\n * // Play continuous spell animation\n * player.setAnimation('spell');\n * ```\n */\n setAnimation(animationName: string, nbTimes?: number, options?: AnimationRestoreOptions): Promise<void>;\n /**\n * Set a custom animation with temporary graphic change\n *\n * Plays a custom animation for the specified number of repetitions and temporarily\n * changes the player's graphic (sprite sheet) during the animation. The graphic\n * is automatically reset when the animation finishes.\n *\n * @param animationName - Name of the animation to play\n * @param graphic - The graphic(s) to temporarily use during the animation\n * @param nbTimes - Number of times to repeat the animation (default: Infinity for continuous)\n * @param options - Restore and timeout options\n * @returns A promise resolved when a finite animation finishes, is interrupted, or times out\n *\n * @example\n * ```ts\n * // Play attack animation with temporary graphic change\n * await player.setAnimation('attack', 'hero_attack', 3);\n * ```\n */\n setAnimation(animationName: string, graphic?: string | string[], nbTimes?: number, options?: AnimationRestoreOptions): Promise<void>;\n setAnimation(\n animationName: string,\n graphicOrNbTimes?: string | string[] | number,\n nbTimesOrOptions?: number | AnimationRestoreOptions,\n options?: AnimationRestoreOptions\n ): Promise<void> {\n let graphic: string | string[] | undefined;\n let finalNbTimes: number = Infinity;\n let restoreOptions: AnimationRestoreOptions | undefined = options;\n\n // Handle overloads\n if (typeof graphicOrNbTimes === 'number') {\n // setAnimation(animationName, nbTimes)\n finalNbTimes = graphicOrNbTimes;\n restoreOptions = typeof nbTimesOrOptions === 'object' ? nbTimesOrOptions : options;\n } else if (graphicOrNbTimes !== undefined) {\n // setAnimation(animationName, graphic, nbTimes)\n graphic = graphicOrNbTimes;\n if (typeof nbTimesOrOptions === 'number') {\n finalNbTimes = nbTimesOrOptions;\n } else {\n finalNbTimes = Infinity;\n restoreOptions = nbTimesOrOptions ?? options;\n }\n } else {\n // setAnimation(animationName) - nbTimes remains Infinity\n finalNbTimes = Infinity;\n }\n\n if (this.animationIsPlaying()) {\n this.finishTemporaryAnimation();\n }\n\n const waitPromise =\n finalNbTimes === Infinity\n ? Promise.resolve()\n : new Promise<void>((resolve) => {\n this.animationWaitResolve = resolve;\n });\n\n this.animationIsPlaying.set(true);\n const previousAnimationName =\n restoreOptions?.restoreAnimationName ?? this.animationName();\n const previousGraphics = restoreOptions?.restoreGraphics\n ? [...restoreOptions.restoreGraphics]\n : [...this.graphics()];\n this.animationRestoreState = {\n animationName: previousAnimationName,\n graphics: previousGraphics,\n };\n this.animationCurrentIndex.set(0);\n\n // Temporarily change graphic if provided\n if (graphic !== undefined) {\n if (Array.isArray(graphic)) {\n this.graphics.set(graphic);\n } else {\n this.graphics.set([graphic]);\n }\n }\n\n this.clearAnimationControls();\n\n this.animationSubscription =\n this.animationCurrentIndex.observable.subscribe((index) => {\n if (index >= finalNbTimes) {\n this.finishTemporaryAnimation();\n }\n });\n\n if (finalNbTimes !== Infinity) {\n this.animationResetTimeout = setTimeout(() => {\n if (this.animationIsPlaying()) {\n this.finishTemporaryAnimation();\n }\n }, restoreOptions?.timeoutMs ?? Math.max(1000, finalNbTimes * 1000));\n }\n\n this.animationName.set(animationName);\n\n return waitPromise;\n }\n\n /**\n * Display a registered component animation effect on this object.\n *\n * @param id - Identifier of the component animation to play.\n * @param params - Parameters forwarded to the animation effect.\n * @returns A promise resolved when the animation component calls `onFinish`.\n */\n showComponentAnimation(id: string, params: any): Promise<void> {\n const engine = inject(RpgClientEngine);\n return engine.getComponentAnimation(id).displayEffect(params, this);\n }\n\n /**\n * Display a registered spritesheet animation effect on this object.\n *\n * @param graphic - Identifier of the spritesheet to use.\n * @param animationName - Name of the animation inside the spritesheet.\n * @returns A promise resolved when the animation component calls `onFinish`.\n */\n showAnimation(graphic: string, animationName: string = 'default'): Promise<void> {\n return this.showComponentAnimation('animation', {\n graphic,\n animationName,\n });\n }\n \n /**\n * Check whether this client object represents an event.\n *\n * @returns `true` if the object type is `event`, otherwise `false`.\n */\n isEvent(): boolean {\n return this._type === 'event';\n }\n\n /**\n * Check whether this client object represents a player.\n *\n * @returns `true` if the object type is `player`, otherwise `false`.\n */\n isPlayer(): boolean {\n return this._type === 'player';\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Object.js","names":[],"sources":["../../src/Game/Object.ts"],"sourcesContent":["import { Hooks, ModulesToken, RpgCommonPlayer } from \"@rpgjs/common\";\nimport { trigger, signal, type Trigger } from \"canvasengine\";\nimport { combineLatest, from, map, of, startWith, Subscription, switchMap } from \"rxjs\";\nimport { inject } from \"../core/inject\";\nimport { RpgClientEngine } from \"../RpgClientEngine\";\ntype Frame = { x: number; y: number; ts: number };\n\ntype AnimationRestoreOptions = {\n restoreAnimationName?: string;\n restoreGraphics?: any[];\n timeoutMs?: number;\n};\n\ntype FlashType = 'alpha' | 'tint' | 'both';\n\ntype FlashOptions = {\n type?: FlashType;\n duration?: number;\n cycles?: number;\n alpha?: number;\n tint?: number | string;\n};\n\ntype FlashTriggerOptions = Omit<FlashOptions, \"tint\"> & {\n tint: number;\n};\n\ntype ConfigurableTrigger<T> = Omit<Trigger<T>, \"start\"> & {\n start(config?: T): Promise<void>;\n};\n\nexport const withGraphicDisplayScale = (spritesheet: any, scale: unknown): any => {\n if (!spritesheet || typeof spritesheet !== \"object\") return spritesheet;\n if (scale === undefined || scale === null) return spritesheet;\n return {\n ...spritesheet,\n displayScale: scale,\n };\n};\n\nexport const appendFramePayload = (current: unknown, items: unknown): Frame[] => {\n const frameItems = Array.isArray(items) ? items : items ? [items] : [];\n const nextFrames = frameItems.flatMap((item): Frame[] =>\n Array.isArray(item) ? item : [item as Frame]\n );\n const currentFrames = Array.isArray(current) ? current as Frame[] : [];\n return currentFrames.concat(nextFrames);\n};\n\nexport abstract class RpgClientObject extends RpgCommonPlayer {\n abstract _type: string;\n emitParticleTrigger = trigger();\n particleName = signal(\"\");\n animationCurrentIndex = signal(0);\n animationIsPlaying = signal(false);\n _param = signal({});\n frames: Frame[] = [];\n graphicsSignals = signal<any[]>([]);\n flashTrigger: ConfigurableTrigger<FlashTriggerOptions> = trigger<FlashTriggerOptions>();\n private animationRestoreState?: {\n animationName: string;\n graphics: any[];\n };\n\n constructor() {\n super();\n const engine = this.engine;\n this.hooks.callHooks(\"client-sprite-onInit\", this).subscribe();\n\n this._frames.observable.subscribe(({ items }) => {\n if (!this.id) return;\n //if (this.id == this.engine.playerIdSignal()!) return;\n this.frames = appendFramePayload(this.frames, items);\n });\n\n const graphics$ = this.graphics.observable.pipe(map(({ items }) => items));\n const graphicScale$ = this._graphicScale.observable.pipe(\n startWith({ value: this._graphicScale() }),\n map((payload: any) => payload?.value ?? payload),\n );\n\n combineLatest([graphics$, graphicScale$])\n .pipe(\n switchMap(([graphics, scale]) => {\n const graphicRefs = Array.isArray(graphics) ? graphics : [];\n if (graphicRefs.length === 0) return of([]);\n return from(Promise.all(graphicRefs.map(async (graphic) => {\n const spritesheet = await engine.getSpriteSheet(graphic);\n return withGraphicDisplayScale(spritesheet, scale);\n })));\n })\n )\n .subscribe((sheets) => { \n this.graphicsSignals.set(sheets);\n });\n\n engine.tick\n .pipe\n //throttleTime(10)\n ()\n .subscribe(() => {\n const frame = this.frames.shift();\n if (frame) {\n if (typeof frame.x !== \"number\" || typeof frame.y !== \"number\") return;\n engine.scene.setBodyPosition(\n this.id,\n frame.x,\n frame.y,\n \"top-left\"\n );\n }\n });\n }\n\n /**\n * Access the shared client hook registry.\n *\n * @returns The hook service used to register and trigger client-side hooks.\n */\n get hooks() {\n return inject<Hooks>(ModulesToken);\n }\n\n /**\n * Access the current client engine instance.\n *\n * @returns The active {@link RpgClientEngine} instance.\n */\n get engine() {\n return inject(RpgClientEngine);\n }\n\n private animationSubscription?: Subscription;\n private animationResetTimeout?: ReturnType<typeof setTimeout>;\n private animationWaitResolve?: () => void;\n\n private clearAnimationControls() {\n if (this.animationSubscription) {\n this.animationSubscription.unsubscribe();\n this.animationSubscription = undefined;\n }\n if (this.animationResetTimeout) {\n clearTimeout(this.animationResetTimeout);\n this.animationResetTimeout = undefined;\n }\n }\n\n private resolveAnimationWait() {\n const resolve = this.animationWaitResolve;\n this.animationWaitResolve = undefined;\n resolve?.();\n }\n\n private finishTemporaryAnimation() {\n const restoreState = this.animationRestoreState;\n this.clearAnimationControls();\n this.animationCurrentIndex.set(0);\n this.animationRestoreState = undefined;\n this.animationIsPlaying.set(false);\n if (restoreState) {\n this.animationName.set(restoreState.animationName);\n this.graphics.set([...restoreState.graphics]);\n }\n this.resolveAnimationWait();\n }\n\n /**\n * Trigger a flash animation on this sprite\n * \n * This method triggers a flash effect using CanvasEngine's flash directive.\n * The flash can be configured with various options including type (alpha, tint, or both),\n * duration, cycles, and color.\n * \n * ## Design\n * \n * The flash uses a trigger system that is connected to the flash directive in the\n * character component. This allows for flexible configuration and can be triggered\n * from both server events and client-side code.\n * \n * @param options - Flash configuration options\n * @param options.type - Type of flash effect: 'alpha' (opacity), 'tint' (color), or 'both' (default: 'alpha')\n * @param options.duration - Duration of the flash animation in milliseconds (default: 300)\n * @param options.cycles - Number of flash cycles (flash on/off) (default: 1)\n * @param options.alpha - Alpha value when flashing, from 0 to 1 (default: 0.3)\n * @param options.tint - Tint color when flashing as hex value or color name (default: 0xffffff - white)\n * \n * @example\n * ```ts\n * // Simple flash with default settings (alpha flash)\n * player.flash();\n * \n * // Flash with red tint\n * player.flash({ type: 'tint', tint: 0xff0000 });\n * \n * // Flash with both alpha and tint\n * player.flash({ \n * type: 'both', \n * alpha: 0.5, \n * tint: 0xff0000,\n * duration: 200,\n * cycles: 2\n * });\n * \n * // Quick damage flash\n * player.flash({ \n * type: 'tint', \n * tint: 0xff0000, \n * duration: 150,\n * cycles: 1\n * });\n * ```\n */\n flash(options?: FlashOptions): void {\n const flashOptions = {\n type: options?.type || 'alpha',\n duration: options?.duration ?? 300,\n cycles: options?.cycles ?? 1,\n alpha: options?.alpha ?? 0.3,\n tint: options?.tint ?? 0xffffff,\n };\n \n // Convert color name to hex if needed\n let tintValue = flashOptions.tint;\n if (typeof tintValue === 'string') {\n // Common color name to hex mapping\n const colorMap: Record<string, number> = {\n 'white': 0xffffff,\n 'red': 0xff0000,\n 'green': 0x00ff00,\n 'blue': 0x0000ff,\n 'yellow': 0xffff00,\n 'cyan': 0x00ffff,\n 'magenta': 0xff00ff,\n 'black': 0x000000,\n };\n tintValue = colorMap[tintValue.toLowerCase()] ?? 0xffffff;\n }\n \n this.flashTrigger.start({\n ...flashOptions,\n tint: tintValue,\n });\n }\n\n /**\n * Reset animation state when animation changes externally\n *\n * This method should be called when the animation changes due to movement\n * or other external factors to ensure the animation system doesn't get stuck\n *\n * @example\n * ```ts\n * // Reset when player starts moving\n * player.resetAnimationState();\n * ```\n */\n resetAnimationState() {\n if (this.animationRestoreState) {\n this.finishTemporaryAnimation();\n return;\n }\n this.animationIsPlaying.set(false);\n this.animationCurrentIndex.set(0);\n this.clearAnimationControls();\n this.resolveAnimationWait();\n }\n\n /**\n * Set a custom animation for a specific number of times\n *\n * Plays a custom animation for the specified number of repetitions.\n * The animation system prevents overlapping animations and automatically\n * returns to the previous animation when complete.\n *\n * @param animationName - Name of the animation to play\n * @param nbTimes - Number of times to repeat the animation (default: Infinity for continuous)\n * @param options - Restore and timeout options\n * @returns A promise resolved when a finite animation finishes, is interrupted, or times out\n *\n * @example\n * ```ts\n * // Play attack animation 3 times\n * await player.setAnimation('attack', 3);\n *\n * // Play continuous spell animation\n * player.setAnimation('spell');\n * ```\n */\n setAnimation(animationName: string, nbTimes?: number, options?: AnimationRestoreOptions): Promise<void>;\n /**\n * Set a custom animation with temporary graphic change\n *\n * Plays a custom animation for the specified number of repetitions and temporarily\n * changes the player's graphic (sprite sheet) during the animation. The graphic\n * is automatically reset when the animation finishes.\n *\n * @param animationName - Name of the animation to play\n * @param graphic - The graphic(s) to temporarily use during the animation\n * @param nbTimes - Number of times to repeat the animation (default: Infinity for continuous)\n * @param options - Restore and timeout options\n * @returns A promise resolved when a finite animation finishes, is interrupted, or times out\n *\n * @example\n * ```ts\n * // Play attack animation with temporary graphic change\n * await player.setAnimation('attack', 'hero_attack', 3);\n * ```\n */\n setAnimation(animationName: string, graphic?: string | string[], nbTimes?: number, options?: AnimationRestoreOptions): Promise<void>;\n setAnimation(\n animationName: string,\n graphicOrNbTimes?: string | string[] | number,\n nbTimesOrOptions?: number | AnimationRestoreOptions,\n options?: AnimationRestoreOptions\n ): Promise<void> {\n let graphic: string | string[] | undefined;\n let finalNbTimes: number = Infinity;\n let restoreOptions: AnimationRestoreOptions | undefined = options;\n\n // Handle overloads\n if (typeof graphicOrNbTimes === 'number') {\n // setAnimation(animationName, nbTimes)\n finalNbTimes = graphicOrNbTimes;\n restoreOptions = typeof nbTimesOrOptions === 'object' ? nbTimesOrOptions : options;\n } else if (graphicOrNbTimes !== undefined) {\n // setAnimation(animationName, graphic, nbTimes)\n graphic = graphicOrNbTimes;\n if (typeof nbTimesOrOptions === 'number') {\n finalNbTimes = nbTimesOrOptions;\n } else {\n finalNbTimes = Infinity;\n restoreOptions = nbTimesOrOptions ?? options;\n }\n } else {\n // setAnimation(animationName) - nbTimes remains Infinity\n finalNbTimes = Infinity;\n }\n\n if (this.animationIsPlaying()) {\n this.finishTemporaryAnimation();\n }\n\n const waitPromise =\n finalNbTimes === Infinity\n ? Promise.resolve()\n : new Promise<void>((resolve) => {\n this.animationWaitResolve = resolve;\n });\n\n this.animationIsPlaying.set(true);\n const previousAnimationName =\n restoreOptions?.restoreAnimationName ?? this.animationName();\n const previousGraphics = restoreOptions?.restoreGraphics\n ? [...restoreOptions.restoreGraphics]\n : [...this.graphics()];\n this.animationRestoreState = {\n animationName: previousAnimationName,\n graphics: previousGraphics,\n };\n this.animationCurrentIndex.set(0);\n\n // Temporarily change graphic if provided\n if (graphic !== undefined) {\n if (Array.isArray(graphic)) {\n this.graphics.set(graphic);\n } else {\n this.graphics.set([graphic]);\n }\n }\n\n this.clearAnimationControls();\n\n this.animationSubscription =\n this.animationCurrentIndex.observable.subscribe((index) => {\n if (index >= finalNbTimes) {\n this.finishTemporaryAnimation();\n }\n });\n\n if (finalNbTimes !== Infinity) {\n this.animationResetTimeout = setTimeout(() => {\n if (this.animationIsPlaying()) {\n this.finishTemporaryAnimation();\n }\n }, restoreOptions?.timeoutMs ?? Math.max(1000, finalNbTimes * 1000));\n }\n\n this.animationName.set(animationName);\n\n return waitPromise;\n }\n\n /**\n * Display a registered component animation effect on this object.\n *\n * @param id - Identifier of the component animation to play.\n * @param params - Parameters forwarded to the animation effect.\n * @returns A promise resolved when the animation component calls `onFinish`.\n */\n showComponentAnimation(id: string, params: any): Promise<void> {\n const engine = inject(RpgClientEngine);\n return engine.getComponentAnimation(id).displayEffect(params, this);\n }\n\n /**\n * Display a registered spritesheet animation effect on this object.\n *\n * @param graphic - Identifier of the spritesheet to use.\n * @param animationName - Name of the animation inside the spritesheet.\n * @returns A promise resolved when the animation component calls `onFinish`.\n */\n showAnimation(graphic: string, animationName: string = 'default'): Promise<void> {\n return this.showComponentAnimation('animation', {\n graphic,\n animationName,\n });\n }\n \n /**\n * Check whether this client object represents an event.\n *\n * @returns `true` if the object type is `event`, otherwise `false`.\n */\n isEvent(): boolean {\n return this._type === 'event';\n }\n\n /**\n * Check whether this client object represents a player.\n *\n * @returns `true` if the object type is `player`, otherwise `false`.\n */\n isPlayer(): boolean {\n return this._type === 'player';\n }\n}\n"],"mappings":"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"}
@@ -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;
@@ -90,6 +93,8 @@ export declare class RpgClientEngine<T = any> {
90
93
  private mapTransitionInProgress;
91
94
  private currentMapRoomId?;
92
95
  private socketListenersInitialized;
96
+ private clientReadyForMapChanges;
97
+ private pendingMapChanges;
93
98
  private tickSubscriptions;
94
99
  private resizeHandler?;
95
100
  private pointerMoveHandler?;
@@ -101,6 +106,7 @@ export declare class RpgClientEngine<T = any> {
101
106
  private i18nService;
102
107
  private locale?;
103
108
  constructor(context: any);
109
+ private resolveLocalMovementAuthority;
104
110
  setLocale(locale: string): void;
105
111
  getLocale(): string;
106
112
  t(key: string, params?: I18nParams): string;
@@ -143,11 +149,17 @@ export declare class RpgClientEngine<T = any> {
143
149
  */
144
150
  setKeyboardControls(controlInstance: any): void;
145
151
  start(): Promise<void>;
152
+ private installCanvasResizeGuard;
153
+ private readCanvasResizeTargetSize;
154
+ private readCanvasRendererSize;
155
+ private cancelCanvasResizeFrame;
146
156
  private resolveSceneMapComponent;
147
157
  private setupPointerTracking;
148
158
  updatePointerFromInteractionEvent(event: any): void;
149
159
  private findViewportInstance;
150
160
  private prepareSyncPayload;
161
+ private shouldPreserveLocalPlayerPosition;
162
+ private shouldKeepLocalPlayerMovement;
151
163
  private normalizeAckWithSyncState;
152
164
  private initListeners;
153
165
  private beginMapTransfer;
@@ -155,6 +167,8 @@ export declare class RpgClientEngine<T = any> {
155
167
  private shouldProcessProjectilePacket;
156
168
  private callConnectError;
157
169
  private flushPendingSyncPackets;
170
+ private flushPendingMapChanges;
171
+ private handleChangeMap;
158
172
  private applySyncPacket;
159
173
  /**
160
174
  * Start periodic ping/pong for client-server synchronization
@@ -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;
@@ -132,6 +135,8 @@ var RpgClientEngine = class {
132
135
  this.sceneResetQueued = false;
133
136
  this.mapTransitionInProgress = false;
134
137
  this.socketListenersInitialized = false;
138
+ this.clientReadyForMapChanges = false;
139
+ this.pendingMapChanges = [];
135
140
  this.tickSubscriptions = [];
136
141
  this.pendingSyncPackets = [];
137
142
  this.notificationManager = new NotificationManager();
@@ -162,8 +167,16 @@ var RpgClientEngine = class {
162
167
  this.registerSpriteComponent("rpg:shape", __ce_component$4);
163
168
  this.registerSpriteComponent("rpg:image", __ce_component$5);
164
169
  this.predictionEnabled = this.globalConfig?.prediction?.enabled !== false;
170
+ this.localMovementAuthority = this.resolveLocalMovementAuthority();
165
171
  this.initializePredictionController();
166
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
+ }
167
180
  setLocale(locale) {
168
181
  this.locale = locale;
169
182
  }
@@ -238,6 +251,7 @@ var RpgClientEngine = class {
238
251
  this.selector = document.body.querySelector("#rpg");
239
252
  const bootstrapOptions = this.globalConfig?.bootstrapCanvasOptions;
240
253
  const { app, canvasElement } = await bootstrapCanvas(this.selector, __ce_component, bootstrapOptions);
254
+ this.installCanvasResizeGuard(app);
241
255
  this.canvasApp = app;
242
256
  this.canvasElement = canvasElement;
243
257
  this.renderer = app.renderer;
@@ -266,6 +280,8 @@ var RpgClientEngine = class {
266
280
  this.hooks.callHooks("client-interactions-load", this).subscribe();
267
281
  this.hooks.callHooks("client-sprite-load", this).subscribe();
268
282
  await lastValueFrom(this.hooks.callHooks("client-engine-onStart", this));
283
+ this.clientReadyForMapChanges = true;
284
+ this.flushPendingMapChanges();
269
285
  this.resizeHandler = () => {
270
286
  this.hooks.callHooks("client-engine-onWindowResize", this).subscribe();
271
287
  };
@@ -285,6 +301,45 @@ var RpgClientEngine = class {
285
301
  this.tickSubscriptions.push(tickSubscription);
286
302
  this.startPingPong();
287
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
+ }
288
343
  resolveSceneMapComponent() {
289
344
  const components = this.hooks.getHookFunctions("client-sceneMap-component");
290
345
  const component = components[components.length - 1];
@@ -378,7 +433,8 @@ var RpgClientEngine = class {
378
433
  delete payload.timestamp;
379
434
  const myId = this.playerIdSignal();
380
435
  const players = payload.players;
381
- 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]) {
382
438
  const localPatch = { ...players[myId] };
383
439
  delete localPatch.x;
384
440
  delete localPatch.y;
@@ -391,6 +447,18 @@ var RpgClientEngine = class {
391
447
  }
392
448
  return payload;
393
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
+ }
394
462
  normalizeAckWithSyncState(ack, syncData) {
395
463
  const myId = this.playerIdSignal();
396
464
  if (!myId) return ack;
@@ -423,10 +491,11 @@ var RpgClientEngine = class {
423
491
  console.debug(`[Ping/Pong] RTT: ${this.rtt}ms, ServerTick: ${data.serverTick}, FrameOffset: ${this.frameOffset}`);
424
492
  });
425
493
  this.webSocket.on("changeMap", (data) => {
426
- const nextMapId = typeof data?.mapId === "string" ? data.mapId : void 0;
427
- this.beginMapTransfer(nextMapId);
428
- const transferToken = typeof data?.transferToken === "string" ? data.transferToken : void 0;
429
- this.loadScene(data.mapId, transferToken);
494
+ if (!this.clientReadyForMapChanges) {
495
+ this.pendingMapChanges.push(data);
496
+ return;
497
+ }
498
+ this.handleChangeMap(data);
430
499
  });
431
500
  this.webSocket.on("showComponentAnimation", (data) => {
432
501
  const { params, object, position, id } = data;
@@ -580,6 +649,17 @@ var RpgClientEngine = class {
580
649
  this.pendingSyncPackets = [];
581
650
  packets.forEach((packet) => this.applySyncPacket(packet));
582
651
  }
652
+ flushPendingMapChanges() {
653
+ const packets = this.pendingMapChanges;
654
+ this.pendingMapChanges = [];
655
+ packets.forEach((packet) => this.handleChangeMap(packet));
656
+ }
657
+ handleChangeMap(data) {
658
+ const nextMapId = typeof data?.mapId === "string" ? data.mapId : void 0;
659
+ this.beginMapTransfer(nextMapId);
660
+ const transferToken = typeof data?.transferToken === "string" ? data.transferToken : void 0;
661
+ this.loadScene(data.mapId, transferToken);
662
+ }
583
663
  applySyncPacket(data) {
584
664
  if (data.pId) {
585
665
  this.playerIdSignal.set(data.pId);
@@ -1305,6 +1385,7 @@ var RpgClientEngine = class {
1305
1385
  if (timestamp < this.dashLockedUntil) return;
1306
1386
  this.dashLockedUntil = timestamp + cooldown;
1307
1387
  }
1388
+ this.lastLocalMovementInputAt = timestamp;
1308
1389
  let frame;
1309
1390
  let tick;
1310
1391
  if (this.predictionEnabled && this.prediction) {
@@ -1677,11 +1758,12 @@ var RpgClientEngine = class {
1677
1758
  }
1678
1759
  applyServerAck(ack) {
1679
1760
  this.updateServerTickEstimate(ack.serverTick);
1761
+ const keepLocalMovement = this.shouldKeepLocalPlayerMovement();
1680
1762
  if (this.predictionEnabled && this.prediction) {
1681
1763
  const result = this.prediction.applyServerAck({
1682
1764
  frame: ack.frame,
1683
1765
  serverTick: ack.serverTick,
1684
- state: typeof ack.x === "number" && typeof ack.y === "number" ? {
1766
+ state: !keepLocalMovement && typeof ack.x === "number" && typeof ack.y === "number" ? {
1685
1767
  x: ack.x,
1686
1768
  y: ack.y,
1687
1769
  direction: ack.direction
@@ -1691,6 +1773,7 @@ var RpgClientEngine = class {
1691
1773
  return;
1692
1774
  }
1693
1775
  if (typeof ack.x !== "number" || typeof ack.y !== "number") return;
1776
+ if (keepLocalMovement) return;
1694
1777
  const player = this.getCurrentPlayer();
1695
1778
  const myId = this.playerIdSignal();
1696
1779
  if (!player || !myId) return;