@rpgjs/client 5.0.0-beta.25 → 5.0.0-beta.26
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 +48 -0
- package/dist/Game/Map.d.ts +1 -0
- package/dist/Game/Map.js +5 -0
- package/dist/Game/Map.js.map +1 -1
- package/dist/RpgClient.d.ts +2 -1
- package/dist/RpgClientEngine.d.ts +76 -0
- package/dist/RpgClientEngine.js +59 -4
- package/dist/RpgClientEngine.js.map +1 -1
- package/dist/components/scenes/draw-map.ce.js +5 -2
- package/dist/components/scenes/draw-map.ce.js.map +1 -1
- package/dist/components/scenes/weather-lifecycle-compat.d.ts +0 -0
- package/dist/components/scenes/weather-lifecycle-compat.js +11 -0
- package/dist/components/scenes/weather-lifecycle-compat.js.map +1 -0
- package/dist/components/scenes/weather-tick-lifecycle.d.ts +17 -0
- package/dist/components/scenes/weather-tick-lifecycle.js +44 -0
- package/dist/components/scenes/weather-tick-lifecycle.js.map +1 -0
- package/dist/components/scenes/weather-tick-lifecycle.spec.d.ts +1 -0
- package/dist/i18n.d.ts +1 -0
- package/dist/i18n.js +1 -0
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/dist/services/loadMap.d.ts +10 -2
- package/dist/services/loadMap.js +2 -1
- package/dist/services/loadMap.js.map +1 -1
- package/dist/services/mapStreaming.d.ts +85 -0
- package/dist/services/mapStreaming.js +210 -0
- package/dist/services/mapStreaming.js.map +1 -0
- package/dist/services/mapStreaming.spec.d.ts +1 -0
- package/dist/services/mmorpg.d.ts +6 -0
- package/dist/services/mmorpg.js +2 -2
- package/dist/services/mmorpg.js.map +1 -1
- package/package.json +3 -3
- package/src/Game/Map.ts +7 -0
- package/src/RpgClient.ts +2 -1
- package/src/RpgClientEngine.ts +147 -6
- package/src/components/scenes/draw-map.ce +4 -1
- package/src/components/scenes/weather-lifecycle-compat.ts +8 -0
- package/src/components/scenes/weather-tick-lifecycle.spec.ts +60 -0
- package/src/components/scenes/weather-tick-lifecycle.ts +69 -0
- package/src/i18n.spec.ts +2 -1
- package/src/i18n.ts +1 -0
- package/src/index.ts +2 -0
- package/src/services/loadMap.ts +9 -2
- package/src/services/mapStreaming.spec.ts +240 -0
- package/src/services/mapStreaming.ts +275 -0
- package/src/services/mmorpg.ts +15 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# @rpgjs/client
|
|
2
2
|
|
|
3
|
+
## 5.0.0-beta.26
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 83fc2b7: Add production Signe room adapters for persistent Node servers and Cloudflare Durable Objects, plus remote Vite map publication for trusted editor workflows with transient-only retries. Resolve local TMX files and their external tilesets when publishing maps, recreate configured events safely during live map updates, and restore player and event graphics after clients reload the Tiled scene.
|
|
8
|
+
|
|
9
|
+
Add provider-neutral authoritative map streaming with progressive render/physics chunks, client prediction barriers, and spatial interest management for players, NPCs, events, and projectiles. Keep complete Tiled TMX/TSX sources server-side in MMORPG mode while sharing the same game module between Node.js, local Wrangler, and Cloudflare Durable Object hosts.
|
|
10
|
+
|
|
11
|
+
Use the physics broad-phase index when resolving synchronized entities in each player's retained chunks, avoiding full-room player and event scans on every sync packet.
|
|
12
|
+
|
|
13
|
+
Assign Tiled collision geometry to every streamed chunk it intersects, and preserve the generated client reference for action, dash, pointer, and interaction APIs with a dedicated interactions guide.
|
|
14
|
+
|
|
15
|
+
Preserve initial room synchronization on older local Workerd runtimes that expose an accepted Durable Object WebSocket with a transient `CONNECTING` ready state.
|
|
16
|
+
|
|
17
|
+
Prevent CanvasEngine rain layers from retaining tick subscriptions when an asynchronous mount overlaps destruction or another mount.
|
|
18
|
+
|
|
19
|
+
Keep component-ready standalone Studio maps intact when sharing the in-memory server, and select the Cloudflare publisher independently from the generic MMORPG entry so local Node.js development does not require a Worker secret.
|
|
20
|
+
|
|
21
|
+
Add Studio v2 authoritative map preparation and progressive chunk streaming, including server physics, nearby rendering data, and provider-neutral entity synchronization. Trusted Vite publishers can now resolve a complete Studio payload before sending it to Node or Cloudflare map rooms, while raw Studio map structure, events, database records, and global collision data stay server-side.
|
|
22
|
+
|
|
23
|
+
Make initial map streaming explicit and hibernation-safe: clients request a fresh manifest after joining, trusted map updates are durably stored before acknowledgement, and recreated Durable Object room instances rebuild their transient streaming runtime. Stream Studio terrain control masks per chunk so transition rendering and prediction physics work without disclosing the complete map, and reset spatial visibility on reconnect so existing clients receive newly joined players.
|
|
24
|
+
|
|
25
|
+
Preserve custom streaming providers when Studio's built-in streaming is disabled, refresh cached client controllers after Durable Object hibernation, and key terrain-control buffers by their complete streamed region content to prevent stale masks.
|
|
26
|
+
|
|
27
|
+
Suppress projectile lifecycle packets that fall outside a player's disclosed interest window, clear client prediction barriers after the final streamed chunk is evicted, and coalesce concurrent requests for the same map stream. Exercise these paths with server, client, and real Workerd WebSocket tests, and run the Cloudflare MMORPG and Studio runtime suites in CI.
|
|
28
|
+
|
|
29
|
+
Avoid dereferencing an empty Studio weather state while switching maps.
|
|
30
|
+
|
|
31
|
+
Publish authenticated world topology updates to every map room, persist them across Durable Object hibernation, and refresh automatic world-map transitions without restarting the MMORPG server.
|
|
32
|
+
|
|
33
|
+
Document the Durable Object room model, map-and-world publication flow, hibernation recovery, production deployment, and common Cloudflare diagnostics.
|
|
34
|
+
|
|
35
|
+
Correct the documented default runtime to standalone RPG and add a beginner deployment path that takes the v5 starter through explicit MMORPG development, private map bundling, authenticated map publication, a persistent Node Docker deployment, or a Cloudflare Durable Object deployment. Include an executable production map publisher in the Cloudflare sample and make the production pages discoverable from both documentation navigations.
|
|
36
|
+
|
|
37
|
+
Declare the RPGJS Durable Object binding explicitly in Wrangler staging and production environments so isolated deployments keep their room namespace.
|
|
38
|
+
|
|
39
|
+
Cover the previous Studio scene before unmounting it during World transfers, preserve recent directional movement into the destination room, then reveal the new map through a full-screen dark transition with a centered, delayed localized loader and a bounded asset wait so stale or white frames cannot flash while fast local transitions stay unobtrusive.
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- b6ab003: Establish `defineModule()` as the canonical runtime module authoring API, export it from the client and server packages, keep `createModule()` for advanced provider composition, and align runtime-specific module installation documentation and examples.
|
|
44
|
+
- Updated dependencies [b6ab003]
|
|
45
|
+
- Updated dependencies [777541a]
|
|
46
|
+
- Updated dependencies [48fcd25]
|
|
47
|
+
- Updated dependencies [83fc2b7]
|
|
48
|
+
- @rpgjs/common@5.0.0-beta.25
|
|
49
|
+
- @rpgjs/server@5.0.0-beta.26
|
|
50
|
+
|
|
3
51
|
## 5.0.0-beta.25
|
|
4
52
|
|
|
5
53
|
### Minor Changes
|
package/dist/Game/Map.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare class RpgClientMap extends RpgCommonMap<any> {
|
|
|
9
9
|
currentPlayer: import('canvasengine').ComputedSignal<RpgClientPlayer>;
|
|
10
10
|
weatherState: import('canvasengine').WritableSignal<WeatherState | null>;
|
|
11
11
|
localWeatherOverride: import('canvasengine').WritableSignal<WeatherState | null>;
|
|
12
|
+
private localWeatherValue;
|
|
12
13
|
lightingState: import('canvasengine').WritableSignal<LightingState | null>;
|
|
13
14
|
localLightSpots: import('canvasengine').WritableObjectSignal<Record<string, LightSpot>>;
|
|
14
15
|
weather: import('canvasengine').ComputedSignal<WeatherState | null>;
|
package/dist/Game/Map.js
CHANGED
|
@@ -25,6 +25,7 @@ var RpgClientMap = class extends RpgCommonMap {
|
|
|
25
25
|
this.currentPlayer = computed(() => this.players()[this.engine.playerIdSignal()]);
|
|
26
26
|
this.weatherState = signal(null);
|
|
27
27
|
this.localWeatherOverride = signal(null);
|
|
28
|
+
this.localWeatherValue = null;
|
|
28
29
|
this.lightingState = signal(null);
|
|
29
30
|
this.localLightSpots = signal({});
|
|
30
31
|
this.weather = computed(() => {
|
|
@@ -72,6 +73,7 @@ var RpgClientMap = class extends RpgCommonMap {
|
|
|
72
73
|
this.players.set(currentPlayerId && currentPlayer ? { [currentPlayerId]: currentPlayer } : {});
|
|
73
74
|
this.events.set({});
|
|
74
75
|
this.weatherState.set(null);
|
|
76
|
+
this.localWeatherValue = null;
|
|
75
77
|
this.localWeatherOverride.set(null);
|
|
76
78
|
this.lightingState.set(null);
|
|
77
79
|
this.localLightSpots.set({});
|
|
@@ -81,9 +83,12 @@ var RpgClientMap = class extends RpgCommonMap {
|
|
|
81
83
|
return this.weather();
|
|
82
84
|
}
|
|
83
85
|
setLocalWeather(next) {
|
|
86
|
+
if (this.localWeatherValue === next) return;
|
|
87
|
+
this.localWeatherValue = next;
|
|
84
88
|
this.localWeatherOverride.set(next);
|
|
85
89
|
}
|
|
86
90
|
clearLocalWeather() {
|
|
91
|
+
this.localWeatherValue = null;
|
|
87
92
|
this.localWeatherOverride.set(null);
|
|
88
93
|
}
|
|
89
94
|
getLighting() {
|
package/dist/Game/Map.js.map
CHANGED
|
@@ -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\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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"}
|
|
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 private localWeatherValue: 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.localWeatherValue = 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 if (this.localWeatherValue === next) {\n return;\n }\n this.localWeatherValue = next;\n this.localWeatherOverride.set(next);\n }\n\n clearLocalWeather(): void {\n this.localWeatherValue = null;\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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"}
|
package/dist/RpgClient.d.ts
CHANGED
|
@@ -230,7 +230,8 @@ export interface RpgSceneHooks<Scene> {
|
|
|
230
230
|
*/
|
|
231
231
|
onRemoveSprite?: (scene: Scene, sprite: RpgComponent) => any;
|
|
232
232
|
/**
|
|
233
|
-
* Before the scene is loaded
|
|
233
|
+
* Before the scene is loaded. Async hooks are awaited while the previous
|
|
234
|
+
* scene is still mounted, allowing transition UI to cover it before teardown.
|
|
234
235
|
*
|
|
235
236
|
* @prop { (scene: RpgScene) => any } [onBeforeLoading]
|
|
236
237
|
* @memberof RpgSceneHooks
|
|
@@ -43,7 +43,40 @@ export declare class RpgClientEngine<T = any> {
|
|
|
43
43
|
componentAnimations: any[];
|
|
44
44
|
clientVisuals: ClientVisualRegistry;
|
|
45
45
|
projectiles: ProjectileManager;
|
|
46
|
+
/**
|
|
47
|
+
* Read the latest pointer position tracked by the client canvas. World
|
|
48
|
+
* coordinates are suitable for action payloads and map interactions.
|
|
49
|
+
*
|
|
50
|
+
* @title pointer
|
|
51
|
+
* @prop pointer: ClientPointerContext
|
|
52
|
+
* @memberof RpgClientEngine
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* const target = engine.pointer.world()
|
|
56
|
+
* if (target) engine.processAction('projectile:shoot', { target })
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
46
59
|
pointer: ClientPointerContext;
|
|
60
|
+
/**
|
|
61
|
+
* Register client-only pointer behaviors for map sprites. Interactions remain
|
|
62
|
+
* local unless a behavior explicitly sends an action to the server.
|
|
63
|
+
*
|
|
64
|
+
* See the [client interactions guide](../../guide/interactions.md) for hover,
|
|
65
|
+
* selection, hit testing, drag-and-drop, overlays, and network rules.
|
|
66
|
+
*
|
|
67
|
+
* @title interactions
|
|
68
|
+
* @prop interactions: RpgClientInteractions
|
|
69
|
+
* @memberof RpgClientEngine
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* engine.interactions.use('Guard', {
|
|
73
|
+
* cursor: 'pointer',
|
|
74
|
+
* click(ctx) {
|
|
75
|
+
* ctx.action('guard:talk', { eventId: ctx.target.id })
|
|
76
|
+
* }
|
|
77
|
+
* })
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
47
80
|
interactions: RpgClientInteractions;
|
|
48
81
|
private spritesheetResolver?;
|
|
49
82
|
private soundResolver?;
|
|
@@ -86,6 +119,8 @@ export declare class RpgClientEngine<T = any> {
|
|
|
86
119
|
private pingInterval;
|
|
87
120
|
private readonly PING_INTERVAL_MS;
|
|
88
121
|
private lastInputTime;
|
|
122
|
+
private latestDirectionalInput?;
|
|
123
|
+
private pendingMapTransferInput?;
|
|
89
124
|
private readonly MOVE_PATH_RESEND_INTERVAL_MS;
|
|
90
125
|
private readonly MAX_MOVE_TRAJECTORY_POINTS;
|
|
91
126
|
private lastMovePathSentAt;
|
|
@@ -98,6 +133,7 @@ export declare class RpgClientEngine<T = any> {
|
|
|
98
133
|
private sceneResetQueued;
|
|
99
134
|
private mapTransitionInProgress;
|
|
100
135
|
private currentMapRoomId?;
|
|
136
|
+
private activeMapStreamController?;
|
|
101
137
|
private socketListenersInitialized;
|
|
102
138
|
private clientReadyForMapChanges;
|
|
103
139
|
private pendingMapChanges;
|
|
@@ -170,6 +206,7 @@ export declare class RpgClientEngine<T = any> {
|
|
|
170
206
|
private normalizeAckWithSyncState;
|
|
171
207
|
private initListeners;
|
|
172
208
|
private beginMapTransfer;
|
|
209
|
+
private resetSceneForMapTransfer;
|
|
173
210
|
private clearComponentAnimations;
|
|
174
211
|
private shouldProcessProjectilePacket;
|
|
175
212
|
private callConnectError;
|
|
@@ -223,6 +260,7 @@ export declare class RpgClientEngine<T = any> {
|
|
|
223
260
|
*/
|
|
224
261
|
private sendPing;
|
|
225
262
|
private loadScene;
|
|
263
|
+
private resumeMapTransferMovement;
|
|
226
264
|
addSpriteSheet<T = any>(spritesheetClass: any, id?: string): any;
|
|
227
265
|
/**
|
|
228
266
|
* Set a resolver function for spritesheets
|
|
@@ -676,7 +714,44 @@ export declare class RpgClientEngine<T = any> {
|
|
|
676
714
|
processInput({ input }: {
|
|
677
715
|
input: RpgMovementInput;
|
|
678
716
|
}): Promise<void>;
|
|
717
|
+
/**
|
|
718
|
+
* Start a predicted dash for the current player and send it through the
|
|
719
|
+
* authoritative movement channel.
|
|
720
|
+
*
|
|
721
|
+
* @title processDash
|
|
722
|
+
* @method processDash(input?: Partial<RpgDashInput>): Promise<void>
|
|
723
|
+
* @param input - Optional direction, speed, duration, and cooldown overrides.
|
|
724
|
+
* @returns A promise resolved after the dash input has been processed locally.
|
|
725
|
+
* @memberof RpgClientEngine
|
|
726
|
+
* @example
|
|
727
|
+
* ```ts
|
|
728
|
+
* await engine.processDash({
|
|
729
|
+
* direction: { x: 1, y: 0 },
|
|
730
|
+
* additionalSpeed: 10,
|
|
731
|
+
* duration: 220,
|
|
732
|
+
* cooldown: 600,
|
|
733
|
+
* })
|
|
734
|
+
* ```
|
|
735
|
+
*/
|
|
679
736
|
processDash(input?: Partial<RpgDashInput>): Promise<void>;
|
|
737
|
+
/**
|
|
738
|
+
* Send an action intent to the authoritative server. Client-provided data
|
|
739
|
+
* must be validated by the receiving player input handler or action.
|
|
740
|
+
*
|
|
741
|
+
* @title processAction
|
|
742
|
+
* @method processAction(action: RpgActionName | RpgActionInput, data?: any): void
|
|
743
|
+
* @param action - Action name/control value, or a normalized action object.
|
|
744
|
+
* @param data - Optional serializable context sent with an action name.
|
|
745
|
+
* @returns Nothing.
|
|
746
|
+
* @memberof RpgClientEngine
|
|
747
|
+
* @example
|
|
748
|
+
* ```ts
|
|
749
|
+
* engine.processAction('projectile:shoot', {
|
|
750
|
+
* target: engine.pointer.world(),
|
|
751
|
+
* source: 'map-click',
|
|
752
|
+
* })
|
|
753
|
+
* ```
|
|
754
|
+
*/
|
|
680
755
|
processAction(action: RpgActionName, data?: any): void;
|
|
681
756
|
processAction(action: RpgActionInput): void;
|
|
682
757
|
get PIXI(): typeof PIXI;
|
|
@@ -742,6 +817,7 @@ export declare class RpgClientEngine<T = any> {
|
|
|
742
817
|
* ```
|
|
743
818
|
*/
|
|
744
819
|
clearClientPredictionStates(): void;
|
|
820
|
+
private clearMapTransferPredictionStates;
|
|
745
821
|
/**
|
|
746
822
|
* Stop local movement immediately and discard pending predicted movement.
|
|
747
823
|
*
|
package/dist/RpgClientEngine.js
CHANGED
|
@@ -241,6 +241,7 @@ var RpgClientEngine = class {
|
|
|
241
241
|
this.sceneMap.configureClientPrediction(this.predictionEnabled);
|
|
242
242
|
this.sceneMap.loadPhysic();
|
|
243
243
|
this.resolveSceneMapComponent();
|
|
244
|
+
this.loadMapService.initialize?.();
|
|
244
245
|
inject(SaveClientService).initialize();
|
|
245
246
|
this.initListeners();
|
|
246
247
|
this.guiService._initialize();
|
|
@@ -624,11 +625,15 @@ var RpgClientEngine = class {
|
|
|
624
625
|
this.callConnectError(error);
|
|
625
626
|
});
|
|
626
627
|
}
|
|
627
|
-
beginMapTransfer(nextMapId) {
|
|
628
|
+
beginMapTransfer(nextMapId, continueMovement = false) {
|
|
629
|
+
this.pendingMapTransferInput = continueMovement ? this.latestDirectionalInput : void 0;
|
|
628
630
|
this.mapTransitionInProgress = true;
|
|
629
631
|
this.currentMapRoomId = nextMapId;
|
|
630
632
|
this.sceneResetQueued = false;
|
|
631
|
-
this.
|
|
633
|
+
this.clearMapTransferPredictionStates();
|
|
634
|
+
}
|
|
635
|
+
resetSceneForMapTransfer(nextMapId) {
|
|
636
|
+
this.sceneMap.data.set(null);
|
|
632
637
|
this.sceneMap.weatherState.set(null);
|
|
633
638
|
this.sceneMap.lightingState.set(null);
|
|
634
639
|
this.sceneMap.clearLightSpots();
|
|
@@ -664,7 +669,7 @@ var RpgClientEngine = class {
|
|
|
664
669
|
}
|
|
665
670
|
handleChangeMap(data) {
|
|
666
671
|
const nextMapId = typeof data?.mapId === "string" ? data.mapId : void 0;
|
|
667
|
-
this.beginMapTransfer(nextMapId);
|
|
672
|
+
this.beginMapTransfer(nextMapId, data?.continueMovement === true);
|
|
668
673
|
const transferToken = typeof data?.transferToken === "string" ? data.transferToken : void 0;
|
|
669
674
|
this.loadScene(data.mapId, transferToken);
|
|
670
675
|
}
|
|
@@ -763,7 +768,11 @@ var RpgClientEngine = class {
|
|
|
763
768
|
}
|
|
764
769
|
async loadScene(mapId, transferToken) {
|
|
765
770
|
await lastValueFrom(this.hooks.callHooks("client-sceneMap-onBeforeLoading", this.sceneMap));
|
|
766
|
-
this.
|
|
771
|
+
this.activeMapStreamController?.detach();
|
|
772
|
+
this.activeMapStreamController = void 0;
|
|
773
|
+
if (this.mapTransitionInProgress) this.resetSceneForMapTransfer(mapId);
|
|
774
|
+
if (this.mapTransitionInProgress) this.clearMapTransferPredictionStates();
|
|
775
|
+
else this.clearClientPredictionStates();
|
|
767
776
|
this.mapLoadCompleted$.next(false);
|
|
768
777
|
this.playerIdReceived$.next(false);
|
|
769
778
|
this.playersReceived$.next(false);
|
|
@@ -778,6 +787,7 @@ var RpgClientEngine = class {
|
|
|
778
787
|
await this.webSocket.reconnect();
|
|
779
788
|
} catch (error) {
|
|
780
789
|
this.mapTransitionInProgress = false;
|
|
790
|
+
this.pendingMapTransferInput = void 0;
|
|
781
791
|
this.stopPingPong();
|
|
782
792
|
await this.callConnectError(error);
|
|
783
793
|
throw error;
|
|
@@ -795,6 +805,23 @@ var RpgClientEngine = class {
|
|
|
795
805
|
this.mapTransitionInProgress = false;
|
|
796
806
|
this.sceneMap.configureClientPrediction(this.predictionEnabled);
|
|
797
807
|
this.sceneMap.loadPhysic();
|
|
808
|
+
if (res?.streamController) {
|
|
809
|
+
const controller = res.streamController;
|
|
810
|
+
this.activeMapStreamController = controller;
|
|
811
|
+
controller.attach(this.sceneMap);
|
|
812
|
+
}
|
|
813
|
+
const transferInput = this.pendingMapTransferInput;
|
|
814
|
+
this.pendingMapTransferInput = void 0;
|
|
815
|
+
if (transferInput !== void 0) this.resumeMapTransferMovement(transferInput, mapId);
|
|
816
|
+
}
|
|
817
|
+
async resumeMapTransferMovement(input, mapId) {
|
|
818
|
+
const repeatCount = 4;
|
|
819
|
+
const repeatIntervalMs = 50;
|
|
820
|
+
for (let index = 0; index < repeatCount; index += 1) {
|
|
821
|
+
if (this.mapTransitionInProgress || this.currentMapRoomId !== mapId) return;
|
|
822
|
+
await this.processInput({ input });
|
|
823
|
+
if (index < repeatCount - 1) await new Promise((resolve) => setTimeout(resolve, repeatIntervalMs));
|
|
824
|
+
}
|
|
798
825
|
}
|
|
799
826
|
addSpriteSheet(spritesheetClass, id) {
|
|
800
827
|
this.spritesheets.set(id || spritesheetClass.id, spritesheetClass);
|
|
@@ -1400,6 +1427,7 @@ var RpgClientEngine = class {
|
|
|
1400
1427
|
const timestamp = Date.now();
|
|
1401
1428
|
const movementInput = isDashInput(input) ? normalizeDashInput(input, currentPlayer?.direction?.()) : input;
|
|
1402
1429
|
if (!movementInput) return;
|
|
1430
|
+
if (!isDashInput(movementInput)) this.latestDirectionalInput = movementInput;
|
|
1403
1431
|
if (isDashInput(movementInput)) {
|
|
1404
1432
|
const cooldown = movementInput.cooldown ?? DEFAULT_DASH_COOLDOWN_MS;
|
|
1405
1433
|
if (timestamp < this.dashLockedUntil) return;
|
|
@@ -1432,6 +1460,25 @@ var RpgClientEngine = class {
|
|
|
1432
1460
|
this.emitMovePacket(movementInput, frame, tick, timestamp, true);
|
|
1433
1461
|
this.lastInputTime = isDashInput(movementInput) ? Date.now() + (movementInput.duration ?? DEFAULT_DASH_DURATION_MS) : Date.now();
|
|
1434
1462
|
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Start a predicted dash for the current player and send it through the
|
|
1465
|
+
* authoritative movement channel.
|
|
1466
|
+
*
|
|
1467
|
+
* @title processDash
|
|
1468
|
+
* @method processDash(input?: Partial<RpgDashInput>): Promise<void>
|
|
1469
|
+
* @param input - Optional direction, speed, duration, and cooldown overrides.
|
|
1470
|
+
* @returns A promise resolved after the dash input has been processed locally.
|
|
1471
|
+
* @memberof RpgClientEngine
|
|
1472
|
+
* @example
|
|
1473
|
+
* ```ts
|
|
1474
|
+
* await engine.processDash({
|
|
1475
|
+
* direction: { x: 1, y: 0 },
|
|
1476
|
+
* additionalSpeed: 10,
|
|
1477
|
+
* duration: 220,
|
|
1478
|
+
* cooldown: 600,
|
|
1479
|
+
* })
|
|
1480
|
+
* ```
|
|
1481
|
+
*/
|
|
1435
1482
|
async processDash(input = {}) {
|
|
1436
1483
|
const currentPlayer = this.sceneMap.getCurrentPlayer();
|
|
1437
1484
|
const dashInput = normalizeDashInput(input, typeof currentPlayer?.direction === "function" ? currentPlayer.direction() : currentPlayer?.direction);
|
|
@@ -1707,6 +1754,14 @@ var RpgClientEngine = class {
|
|
|
1707
1754
|
this.lastMovePathSentAt = 0;
|
|
1708
1755
|
this.lastMovePathSentFrame = 0;
|
|
1709
1756
|
}
|
|
1757
|
+
clearMapTransferPredictionStates() {
|
|
1758
|
+
this.prediction?.clearPendingInputs();
|
|
1759
|
+
this.frameOffset = 0;
|
|
1760
|
+
this.pendingPredictionFrames = [];
|
|
1761
|
+
this.lastClientPhysicsStepAt = 0;
|
|
1762
|
+
this.lastMovePathSentAt = 0;
|
|
1763
|
+
this.lastMovePathSentFrame = this.inputFrameCounter;
|
|
1764
|
+
}
|
|
1710
1765
|
/**
|
|
1711
1766
|
* Stop local movement immediately and discard pending predicted movement.
|
|
1712
1767
|
*
|