@rpgjs/client 5.0.0-beta.24 → 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 +64 -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/Gui/Gui.js +5 -0
- package/dist/Gui/Gui.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/gui/dialogbox/index.ce.js +23 -2
- package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
- package/dist/components/gui/index.d.ts +3 -1
- package/dist/components/gui/index.js +2 -0
- package/dist/components/gui/input-field.ce.js +123 -0
- package/dist/components/gui/input-field.ce.js.map +1 -0
- package/dist/components/gui/input.ce.js +40 -0
- package/dist/components/gui/input.ce.js.map +1 -0
- 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 +12 -0
- package/dist/i18n.js +12 -0
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +15 -11
- 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 +4 -4
- package/src/Game/Map.ts +7 -0
- package/src/Gui/Gui.spec.ts +5 -0
- package/src/Gui/Gui.ts +5 -1
- package/src/RpgClient.ts +2 -1
- package/src/RpgClientEngine.ts +147 -6
- package/src/components/gui/dialogbox/index.ce +26 -1
- package/src/components/gui/index.ts +5 -1
- package/src/components/gui/input-field.ce +106 -0
- package/src/components/gui/input.ce +26 -0
- 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 +12 -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/vite.config.ts +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
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
|
+
|
|
51
|
+
## 5.0.0-beta.25
|
|
52
|
+
|
|
53
|
+
### Minor Changes
|
|
54
|
+
|
|
55
|
+
- 0512640: Add reusable typed input and textarea controls for standalone forms and dialog boxes, with shared server-side validation and an RPGJS Studio block that stores the submitted value.
|
|
56
|
+
|
|
57
|
+
### Patch Changes
|
|
58
|
+
|
|
59
|
+
- ccb9495: Fix TypeScript declaration errors across the package build, align multi-target declaration exports, complete movement API overloads, and make package and root builds fail when declaration generation reports a type error.
|
|
60
|
+
- Updated dependencies [ccb9495]
|
|
61
|
+
- Updated dependencies [f6aa046]
|
|
62
|
+
- Updated dependencies [0512640]
|
|
63
|
+
- @rpgjs/common@5.0.0-beta.24
|
|
64
|
+
- @rpgjs/server@5.0.0-beta.25
|
|
65
|
+
- @rpgjs/ui-css@5.0.0-beta.23
|
|
66
|
+
|
|
3
67
|
## 5.0.0-beta.24
|
|
4
68
|
|
|
5
69
|
### Patch 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":";;;;;;;;;;AA0BA,IAAM,uBACJ,MACA,UACY;CACZ,IAAI,MAAM,QAAQ,IAAI,KAAK,MAAM,QAAQ,KAAK,GAC5C,OAAO,MAAM,QAAQ,IAAI,KACpB,MAAM,QAAQ,KAAK,KACnB,KAAK,WAAW,MAAM,UACtB,KAAK,OAAO,OAAO,UAAU,UAAU,MAAM,MAAM;CAE1D,OAAO,SAAS;AAClB;AAEA,IAAM,mBAAmB,MAA6B,UAA8B;CAClF,IAAI,CAAC,MAAM,OAAO;CAClB,OAAO,KAAK,OAAO,MAAM,MACpB,KAAK,MAAM,MAAM,KACjB,KAAK,MAAM,MAAM,KACjB,KAAK,WAAW,MAAM,UACtB,KAAK,cAAc,MAAM,aACzB,oBAAoB,KAAK,OAAO,MAAM,KAAK,KAC3C,KAAK,YAAY,MAAM,WACvB,KAAK,iBAAiB,MAAM,gBAC5B,KAAK,UAAU,MAAM,SACrB,KAAK,eAAe,MAAM,cAC1B,KAAK,UAAU,MAAM;AAC5B;AAEA,IAAa,eAAb,cAAkC,aAAkB;CAuClD,cAAc;EACZ,MAAM;gBAvCkB,OAAO,eAAe;iBACd,OAAwC,CAAC,CAAC;gBAC7C,OAAuC,CAAC,CAAC;uBACxD,eAAe,KAAK,QAAQ,EAAE,KAAK,OAAO,eAAe,EAAG;sBAC7D,OAA4B,IAAI;8BACxB,OAA4B,IAAI;2BACN;uBACjC,OAA6B,IAAI;yBAC/B,OAAkC,CAAC,CAAC;iBAC5C,eAAoC;GAC5C,MAAM,QAAQ,KAAK,qBAAqB;GACxC,MAAM,QAAQ,KAAK,aAAa;GAChC,OAAO,SAAS;EAClB,CAAC;kBACU,eAAqC;GAC9C,MAAM,QAAQ,mBAAmB,KAAK,cAAc,CAAC;GACrD,MAAM,aAAa,OAAO,QAAQ,KAAK,gBAAgB,CAAC,EAAE,KAAK,CAAC,IAAI,WAAW;IAC7E,GAAG;IACH,IAAI,KAAK,MAAM;GACjB,EAAE;GAEF,IAAI,CAAC,SAAS,WAAW,WAAW,GAClC,OAAO;GAGT,MAAM,OAAsB,EAAE,GAAI,SAAS,CAAC,EAAG;GAC/C,IAAI,CAAC,OAAO,SACV,OAAO,KAAK;GAEd,KAAK,QAAQ,CACX,GAAI,OAAO,SAAS,CAAC,GACrB,GAAG,UACL;GACA,OAAO;EACT,CAAC;iCACiC;EAMhC,MAAM,aAAa;EACnB,MAAM,SAAS,WAAW,SAAS,KAAK,SAAS,UAC5C,WAAW,mBAAmB;EACnC,KAAK,oBAAoB;EACzB,IAAI,QACF,KAAK,kBAAkB;CAE3B;CAEA,0BAA0B,SAAwB;EAChD,KAAK,0BAA0B;EAC/B,KAAK,kBAAkB,UAAU,QAAQ,CAAC,KAAK;CACjD;CAEA,mBAAmB;EACjB,OAAO,KAAK,cAAc;CAC5B;CAEA,MAAM,QAAQ,OAAO;EACnB,MAAM,kBAAkB,KAAK,OAAO,eAAe;EACnD,MAAM,gBAAgB,CAAC,SAAS,kBAC5B,KAAK,QAAQ,EAAE,mBACf,KAAA;EACJ,MAAM,UAAU,KAAK,QAAQ;EAC7B,MAAM,SAAS,KAAK,OAAO;EAE3B,OAAO,QAAQ,OAAO,EAAE,SAAS,CAAC,IAAI,YAAY;GAChD,IAAI,CAAC,UAAW,CAAC,SAAS,OAAO,iBAAkB;GACnD,OAAgB,sBAAsB;EACxC,CAAC;EACD,OAAO,OAAO,MAAM,EAAE,SAAS,UAAU;GACvC,OAAgB,sBAAsB;EACxC,CAAC;EAED,KAAK,QAAQ,IACX,mBAAmB,gBAAgB,GAAG,kBAAkB,cAAc,IAAI,CAAC,CAC7E;EACA,KAAK,OAAO,IAAI,CAAC,CAAC;EAClB,KAAK,aAAa,IAAI,IAAI;EAC1B,KAAK,oBAAoB;EACzB,KAAK,qBAAqB,IAAI,IAAI;EAClC,KAAK,cAAc,IAAI,IAAI;EAC3B,KAAK,gBAAgB,IAAI,CAAC,CAAC;EAC3B,KAAK,YAAY;CACnB;CAEA,aAAkC;EAChC,OAAO,KAAK,QAAQ;CACtB;CAEA,gBAAgB,MAAiC;EAC/C,IAAI,KAAK,sBAAsB,MAC7B;EAEF,KAAK,oBAAoB;EACzB,KAAK,qBAAqB,IAAI,IAAI;CACpC;CAEA,oBAA0B;EACxB,KAAK,oBAAoB;EACzB,KAAK,qBAAqB,IAAI,IAAI;CACpC;CAEA,cAAoC;EAClC,OAAO,KAAK,SAAS;CACvB;CAEA,aAAa,IAAY,MAAuB;EAE9C,MAAM,WADa,uBAAuB,EAAE,OAAO,CAAC;GAAE,GAAG;GAAM;EAAG,CAAC,EAAE,CACpD,GAAY,QAAQ;EACrC,IAAI,CAAC,UACH;EAEF,KAAK,gBAAgB,QAAQ,UAAU;GACrC,IAAI,gBAAgB,MAAM,KAAK,QAAQ,GACrC,OAAO;GAET,OAAO;IACL,GAAG;KACF,KAAK;GACR;EACF,CAAC;CACH;CAEA,eAAe,IAAY,OAAiC;EAC1D,KAAK,gBAAgB,QAAQ,UAAU;GACrC,MAAM,UAAU,MAAM;GACtB,IAAI,CAAC,SACH,OAAO;GAET,OAAO;IACL,GAAG;KACF,KAAK;KACJ,GAAG;KACH,GAAG;KACH;KACA,GAAG,MAAM,KAAK,QAAQ;KACtB,GAAG,MAAM,KAAK,QAAQ;IACxB;GACF;EACF,CAAC;CACH;CAEA,gBAAgB,IAAkB;EAChC,KAAK,gBAAgB,QAAQ,UAAU;GACrC,IAAI,EAAE,MAAM,QACV,OAAO;GAET,MAAM,OAAO,EAAE,GAAG,MAAM;GACxB,OAAO,KAAK;GACZ,OAAO;EACT,CAAC;CACH;CAEA,kBAAwB;EACtB,KAAK,gBAAgB,IAAI,CAAC,CAAC;CAC7B;CAEA,kBAAkB,SAAyB;EACzC,IAAI,CAAC,KAAK,yBACR,OAAO;EAET,OAAO,KAAK,SAAS,OAAO;CAC9B;CAEA,qBAA2B;EACzB,KAAK,gBAAgB;CACvB;CAEA,gBAA0B,SAAsC;EAC9D,KAAK,QAAQ,mBAAmB,iBAAiB,MAAM,OAAO;CAChE;CAEA,qBAA+B,SAAwC;EACrE,KAAK,QAAQ,mBAAmB,sBAAsB,MAAM,OAAO;CACrE;CAEA,wBAAkC,SAAwC;EACxE,KAAK,QAAQ,mBAAmB,yBAAyB,MAAM,OAAO;CACxE;CAEA,mBAAmC;EACjC,KAAK,QAAQ,mBAAmB,kBAAkB,IAAI;CACxD;AACF;YAxLG,MAAM,eAAe,GAAA,mBAAA,eAAA,MAAA,CAAA,GAAA,aAAA,WAAA,WAAA,KAAA,CAAA;YACrB,KAAK,cAAc,GAAA,mBAAA,eAAA,MAAA,CAAA,GAAA,aAAA,WAAA,UAAA,KAAA,CAAA"}
|
package/dist/Gui/Gui.js
CHANGED
|
@@ -7,6 +7,7 @@ import __ce_component$3 from "../components/gui/menu/main-menu.ce.js";
|
|
|
7
7
|
import __ce_component$4 from "../components/gui/notification/notification.ce.js";
|
|
8
8
|
import __ce_component$5 from "../components/gui/title-screen.ce.js";
|
|
9
9
|
import __ce_component$6 from "../components/gui/gameover.ce.js";
|
|
10
|
+
import __ce_component$7 from "../components/gui/input.ce.js";
|
|
10
11
|
import "../components/gui/index.js";
|
|
11
12
|
import { signal } from "canvasengine";
|
|
12
13
|
import { PrebuiltGui } from "@rpgjs/common";
|
|
@@ -113,6 +114,10 @@ var RpgGui = class {
|
|
|
113
114
|
name: PrebuiltGui.Gameover,
|
|
114
115
|
component: __ce_component$6
|
|
115
116
|
});
|
|
117
|
+
this.add({
|
|
118
|
+
name: PrebuiltGui.Input,
|
|
119
|
+
component: __ce_component$7
|
|
120
|
+
});
|
|
116
121
|
this.registerOptimisticReducer(PrebuiltGui.MainMenu, mainMenuOptimisticReducer);
|
|
117
122
|
}
|
|
118
123
|
async _initialize() {
|
package/dist/Gui/Gui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Gui.js","names":[],"sources":["../../src/Gui/Gui.ts"],"sourcesContent":["import { Context, inject } from \"@signe/di\";\nimport { signal, Signal, WritableSignal } from \"canvasengine\";\nimport { AbstractWebsocket, WebSocketToken } from \"../services/AbstractSocket\";\nimport { DialogboxComponent, ShopComponent, SaveLoadComponent, MainMenuComponent, NotificationComponent, TitleScreenComponent, GameoverComponent } from \"../components/gui\";\nimport { combineLatest, Subscription } from \"rxjs\";\nimport { PrebuiltGui } from \"@rpgjs/common\";\n\ninterface GuiOptions {\n name?: string;\n id?: string;\n component?: any;\n display?: boolean;\n data?: any;\n /**\n * Auto display the GUI when added to the system\n * @default false\n */\n autoDisplay?: boolean;\n /**\n * Function that returns an array of Signal dependencies\n * The GUI will only display when all dependencies are resolved (!= undefined)\n * @returns Array of Signal dependencies\n */\n dependencies?: () => Signal[];\n /**\n * Attach the GUI to sprites instead of displaying globally\n * When true, the GUI will be rendered in character.ce for each sprite\n * @default false\n */\n attachToSprite?: boolean;\n /**\n * Vue v4 compatibility flag. Prefer attachToSprite in v5 projects.\n */\n rpgAttachToSprite?: boolean;\n}\n\nexport interface GuiInstance {\n name: string;\n component: any;\n display: WritableSignal<boolean>;\n data: WritableSignal<any>;\n openId?: string;\n autoDisplay: boolean;\n dependencies?: Signal[];\n subscription?: Subscription;\n attachToSprite?: boolean;\n}\n\ntype GuiState = {\n name: string;\n component: any;\n display: boolean;\n data: any;\n openId?: string;\n attachToSprite: boolean;\n};\n\ntype VueGuiBridge = {\n updateGuiState?: (state: GuiState) => void;\n initializeGuiStates?: (states: GuiState[]) => void;\n};\n\ninterface GuiAction {\n guiId: string;\n name: string;\n data: any;\n clientActionId: string;\n}\n\ntype OptimisticReducer = (data: any, action: GuiAction) => any;\n\nconst throwError = (id: string) => {\n throw `The GUI named ${id} is non-existent. Please add the component in the gui property of the decorator @RpgClient`;\n};\n\nconst updateItemQuantity = (items: any[], id: string) => {\n const index = items.findIndex((item) => item?.id === id);\n if (index === -1) return items;\n const item = items[index];\n if (item?.usable === false) return items;\n if (item?.consumable === false) return items;\n const quantity = typeof item?.quantity === \"number\" ? item.quantity : 1;\n const nextQuantity = Math.max(0, quantity - 1);\n if (nextQuantity === quantity) return items;\n if (nextQuantity <= 0) {\n return items.filter((_, idx) => idx !== index);\n }\n const nextItems = items.slice();\n nextItems[index] = { ...item, quantity: nextQuantity };\n return nextItems;\n};\n\nconst updateEquippedFlag = (items: any[], id: string, equip: boolean) => {\n const index = items.findIndex((item) => item?.id === id);\n if (index === -1) return items;\n const item = items[index];\n if (item?.equipped === equip) return items;\n const nextItems = items.slice();\n nextItems[index] = { ...item, equipped: equip };\n return nextItems;\n};\n\nconst mainMenuOptimisticReducer: OptimisticReducer = (data, action) => {\n if (!data || typeof data !== \"object\") return data;\n if (action.name === \"useItem\") {\n if (!Array.isArray(data.items)) return data;\n const id = action.data?.id;\n if (!id) return data;\n const nextItems = updateItemQuantity(data.items, id);\n if (nextItems === data.items) return data;\n return { ...data, items: nextItems };\n }\n if (action.name === \"equipItem\") {\n const id = action.data?.id;\n if (!id || typeof action.data?.equip !== \"boolean\") return data;\n const equip = action.data.equip;\n let nextItems = data.items;\n let nextEquips = data.equips;\n if (Array.isArray(data.items)) {\n nextItems = updateEquippedFlag(data.items, id, equip);\n }\n if (Array.isArray(data.equips)) {\n nextEquips = updateEquippedFlag(data.equips, id, equip);\n }\n if (nextItems === data.items && nextEquips === data.equips) return data;\n return {\n ...data,\n ...(nextItems !== data.items ? { items: nextItems } : {}),\n ...(nextEquips !== data.equips ? { equips: nextEquips } : {})\n };\n }\n return data;\n};\n\nexport class RpgGui {\n private webSocket: AbstractWebsocket;\n gui = signal<Record<string, GuiInstance>>({});\n extraGuis: GuiInstance[] = [];\n private vueGuiInstance: VueGuiBridge | null = null;\n private optimisticReducers = new Map<string, OptimisticReducer[]>();\n private pendingActions = new Map<string, GuiAction[]>();\n /**\n * Signal tracking which player IDs should display attached GUIs\n * Key: player ID, Value: boolean (true = show, false = hide)\n */\n attachedGuiDisplayState = signal<Record<string, boolean>>({});\n\n constructor(private context: Context) {\n this.webSocket = inject(context, WebSocketToken);\n this.add({\n name: \"rpg-dialog\",\n component: DialogboxComponent,\n });\n this.add({\n name: PrebuiltGui.MainMenu,\n component: MainMenuComponent,\n });\n this.add({\n name: PrebuiltGui.Shop,\n component: ShopComponent,\n });\n this.add({\n name: PrebuiltGui.Notification,\n component: NotificationComponent,\n autoDisplay: true,\n });\n this.add({\n name: PrebuiltGui.Save,\n component: SaveLoadComponent,\n });\n this.add({\n name: PrebuiltGui.TitleScreen,\n component: TitleScreenComponent,\n });\n this.add({\n name: PrebuiltGui.Gameover,\n component: GameoverComponent,\n });\n\n this.registerOptimisticReducer(PrebuiltGui.MainMenu, mainMenuOptimisticReducer);\n }\n\n async _initialize() {\n this.webSocket.on(\"gui.open\", (data: { guiId: string; data: any; guiOpenId?: string }) => {\n this.clearPendingActions(data.guiId);\n this.display(data.guiId, data.data, [], data.guiOpenId);\n });\n\n this.webSocket.on(\"gui.exit\", (payload: string | { guiId: string; guiOpenId?: string }) => {\n const guiId = typeof payload === \"string\" ? payload : payload.guiId;\n const guiOpenId = typeof payload === \"string\" ? undefined : payload.guiOpenId;\n const current = this.get(guiId);\n if (guiOpenId && current?.openId && current.openId !== guiOpenId) {\n return;\n }\n this.hide(guiId);\n });\n\n this.webSocket.on(\"gui.update\", (payload: { guiId: string; data: any; clientActionId?: string }) => {\n this.applyServerUpdate(payload.guiId, payload.data, payload.clientActionId);\n });\n\n /**\n * Listen for tooltip display state changes from server\n * This is triggered by showAttachedGui/hideAttachedGui on the server\n */\n this.webSocket.on(\"gui.tooltip\", (data: { players: string[]; display: boolean }) => {\n const currentState = { ...this.attachedGuiDisplayState() };\n data.players.forEach((playerId) => {\n currentState[playerId] = data.display;\n });\n this.attachedGuiDisplayState.set(currentState);\n });\n }\n\n /**\n * Set the VueGui instance reference for Vue component management\n * This is called by VueGui when it's initialized\n * \n * @param vueGuiInstance - The VueGui instance\n */\n _setVueGuiInstance(vueGuiInstance: any) {\n this.vueGuiInstance = vueGuiInstance;\n this._initializeVueComponents();\n }\n\n /**\n * Notify VueGui about GUI state changes\n * This synchronizes the Vue component display state\n * \n * @param guiId - The GUI component ID\n * @param display - Display state\n * @param data - Component data\n */\n private _notifyVueGui(guiId: string, display: boolean, data: any = {}) {\n const extraGui = this.extraGuis.find(gui => gui.name === guiId);\n if (!extraGui) return;\n this.vueGuiInstance?.updateGuiState?.(this.toGuiState(extraGui, display, data));\n }\n\n /**\n * Initialize Vue components in the VueGui instance\n * This should be called after VueGui is mounted\n */\n _initializeVueComponents() {\n this.vueGuiInstance?.initializeGuiStates?.(\n this.extraGuis.map(gui => this.toGuiState(gui))\n );\n }\n\n guiInteraction(guiId: string, name: string, data: any) {\n const clientActionId = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`;\n const actionData = { ...(data || {}), clientActionId };\n this.applyOptimisticAction({\n guiId,\n name,\n data: actionData,\n clientActionId\n });\n this.webSocket.emit(\"gui.interaction\", {\n guiId,\n name,\n data: actionData,\n });\n }\n\n guiClose(guiId: string, data?: any, guiOpenId?: unknown) {\n const normalizedOpenId =\n typeof guiOpenId === \"string\" && guiOpenId.length > 0 ? guiOpenId : undefined;\n this.webSocket.emit(\"gui.exit\", {\n guiId,\n guiOpenId: normalizedOpenId,\n data,\n });\n }\n\n /**\n * Add a GUI component to the system\n * \n * By default, only CanvasEngine components (.ce files) are accepted.\n * Vue components should be handled by the @rpgjs/vue package.\n * \n * @param gui - GUI configuration options\n * @param gui.name - Name or ID of the GUI component\n * @param gui.id - Alternative ID if name is not provided\n * @param gui.component - The component to render (must be a CanvasEngine component)\n * @param gui.display - Initial display state (default: false)\n * @param gui.data - Initial data for the component\n * @param gui.autoDisplay - Auto display when added (default: false)\n * @param gui.dependencies - Function returning Signal dependencies\n * @param gui.attachToSprite - Attach GUI to sprites instead of global display (default: false)\n * \n * @example\n * ```ts\n * gui.add({\n * name: 'inventory',\n * component: InventoryComponent, // Must be a .ce component\n * autoDisplay: true,\n * dependencies: () => [playerSignal, inventorySignal]\n * });\n * \n * // Attach to sprites\n * gui.add({\n * name: 'tooltip',\n * component: TooltipComponent,\n * attachToSprite: true\n * });\n * ```\n */\n add(gui: GuiOptions | any) {\n const component = this.resolveComponent(gui);\n const guiId = this.resolveGuiId(gui, component);\n if (!guiId) {\n throw new Error(\"GUI must have a name or id\");\n }\n const attachToSprite = this.resolveAttachToSprite(gui, component);\n const guiInstance: GuiInstance = {\n name: guiId,\n component,\n display: signal<boolean>(gui.display || false),\n data: signal<any>(gui.data || {}),\n openId: undefined,\n autoDisplay: gui.autoDisplay || false,\n dependencies: gui.dependencies ? gui.dependencies() : [],\n attachToSprite,\n };\n\n if (this.isVueComponentInstance(guiInstance)) {\n this.removeCanvasGui(guiId);\n const existingIndex = this.extraGuis.findIndex(existing => existing.name === guiId);\n if (existingIndex >= 0) {\n this.extraGuis[existingIndex].subscription?.unsubscribe();\n this.extraGuis[existingIndex] = guiInstance;\n } else {\n this.extraGuis.push(guiInstance);\n }\n\n this._initializeVueComponents();\n \n if (guiInstance.autoDisplay) {\n this.display(guiId, gui.data);\n } else {\n this._notifyVueGui(guiId, guiInstance.display(), guiInstance.data());\n }\n return;\n }\n\n this.removeVueGui(guiId);\n this.gui()[guiId] = guiInstance;\n this._initializeVueComponents();\n\n // Auto display if enabled and it's a CanvasEngine component\n if (guiInstance.autoDisplay && typeof gui.component === 'function') {\n this.display(guiId, gui.data);\n }\n }\n\n registerOptimisticReducer(guiId: string, reducer: OptimisticReducer) {\n const existing = this.optimisticReducers.get(guiId) || [];\n this.optimisticReducers.set(guiId, existing.concat(reducer));\n }\n\n /**\n * Get all attached GUI components (attachToSprite: true)\n * \n * Returns all GUI instances that are configured to be attached to sprites.\n * These GUIs should be rendered in character.ce instead of canvas.ce.\n * \n * @returns Array of GUI instances with attachToSprite: true\n * \n * @example\n * ```ts\n * const attachedGuis = gui.getAttachedGuis();\n * // Use in character.ce to render tooltips\n * ```\n */\n getAttachedGuis(): GuiInstance[] {\n return Object.values(this.gui()).filter(gui => gui.attachToSprite === true);\n }\n\n getVueGuis(): GuiInstance[] {\n return [...this.extraGuis];\n }\n\n getAttachedVueGuis(): GuiInstance[] {\n return this.extraGuis.filter(gui => gui.attachToSprite === true);\n }\n\n /**\n * Check if a player should display attached GUIs\n * \n * @param playerId - The player ID to check\n * @returns true if attached GUIs should be displayed for this player\n */\n shouldDisplayAttachedGui(playerId: string): boolean {\n return this.attachedGuiDisplayState()[playerId] === true;\n }\n\n get(id: string): GuiInstance | undefined {\n // Check CanvasEngine GUIs first\n const canvasGui = this.gui()[id];\n if (canvasGui) {\n return canvasGui;\n }\n \n // Check Vue GUIs in extraGuis\n return this.extraGuis.find(gui => gui.name === id);\n }\n\n exists(id: string): boolean {\n return !!this.get(id);\n }\n\n getAll(): Record<string, GuiInstance> {\n const allGuis = { ...this.gui() };\n \n // Add extraGuis to the result\n this.extraGuis.forEach(gui => {\n allGuis[gui.name] = gui;\n });\n \n return allGuis;\n }\n\n /**\n * Display a GUI component\n * \n * Displays the GUI immediately if no dependencies are configured,\n * or waits for all dependencies to be resolved if dependencies are present.\n * Automatically manages subscriptions to prevent memory leaks.\n * Works with both CanvasEngine components and Vue components.\n * \n * @param id - The GUI component ID\n * @param data - Data to pass to the component\n * @param dependencies - Optional runtime dependencies (overrides config dependencies)\n * \n * @example\n * ```ts\n * // Display immediately\n * gui.display('inventory', { items: [] });\n * \n * // Display with runtime dependencies\n * gui.display('shop', { shopId: 1 }, [playerSignal, shopSignal]);\n * ```\n */\n display(id: string, data = {}, dependencies: Signal[] = [], openId?: string) {\n if (!this.exists(id)) {\n throw throwError(id);\n }\n\n const guiInstance = this.get(id)!;\n const isVueComponent = this.extraGuis.some(gui => gui.name === id);\n\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n const show = () => {\n guiInstance.openId = openId;\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n if (isVueComponent) {\n this._notifyVueGui(id, true, data);\n }\n };\n\n const deps = dependencies.length > 0\n ? dependencies\n : (guiInstance.dependencies ?? []);\n\n if (deps.length > 0) {\n const values = deps.map(dependency => dependency());\n const subscription = new Subscription();\n const showIfReady = () => {\n if (values.every(value => value !== undefined)) {\n show();\n }\n };\n\n deps.forEach((dependency, index) => {\n subscription.add(dependency.observable.subscribe((value) => {\n values[index] = value;\n showIfReady();\n }));\n });\n\n guiInstance.subscription = subscription;\n showIfReady();\n return;\n }\n\n show();\n }\n\n isDisplaying(id: string): boolean {\n const guiInstance = this.get(id);\n if (!guiInstance) return false;\n return guiInstance.display();\n }\n\n /**\n * Handle Vue component display logic\n * \n * @param id - GUI component ID\n * @param data - Component data\n * @param dependencies - Runtime dependencies\n * @param guiInstance - GUI instance\n */\n private _handleVueComponentDisplay(id: string, data: any, dependencies: Signal[], guiInstance: GuiInstance, openId?: string) {\n // Unsubscribe from previous subscription if exists\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n // Use runtime dependencies or config dependencies\n const deps = dependencies.length > 0 \n ? dependencies \n : (guiInstance.dependencies ?? []);\n\n if (deps.length > 0) {\n const values = deps.map(dependency => dependency());\n const subscription = new Subscription();\n const showIfReady = () => {\n if (values.every(value => value !== undefined)) {\n guiInstance.openId = openId;\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n this._notifyVueGui(id, true, data);\n }\n };\n\n deps.forEach((dependency, index) => {\n subscription.add(dependency.observable.subscribe((value) => {\n values[index] = value;\n showIfReady();\n }));\n });\n\n guiInstance.subscription = subscription;\n showIfReady();\n return;\n }\n\n // No dependencies, display immediately\n guiInstance.openId = openId;\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n this._notifyVueGui(id, true, data);\n }\n\n /**\n * Hide a GUI component\n * \n * Hides the GUI and cleans up any active subscriptions.\n * Works with both CanvasEngine components and Vue components.\n * \n * @param id - The GUI component ID\n * \n * @example\n * ```ts\n * gui.hide('inventory');\n * ```\n */\n hide(id: string) {\n if (!this.exists(id)) {\n throw throwError(id);\n }\n\n const guiInstance = this.get(id)!;\n \n // Unsubscribe if there's an active subscription\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n guiInstance.display.set(false)\n guiInstance.openId = undefined;\n \n // Check if it's a Vue component and notify VueGui\n const isVueComponent = this.extraGuis.some(gui => gui.name === id);\n if (isVueComponent) {\n this._notifyVueGui(id, false);\n }\n }\n\n private isVueComponent(id: string) {\n return this.extraGuis.some(gui => gui.name === id);\n }\n\n private isVueComponentInstance(gui: GuiInstance) {\n return typeof gui.component !== \"function\";\n }\n\n private removeCanvasGui(guiId: string) {\n const current = this.gui();\n if (!(guiId in current)) return;\n const next = { ...current };\n delete next[guiId];\n this.gui.set(next);\n }\n\n private removeVueGui(guiId: string) {\n const removed = this.extraGuis.filter(existing => existing.name === guiId);\n removed.forEach(gui => gui.subscription?.unsubscribe());\n if (removed.length > 0) {\n this.extraGuis = this.extraGuis.filter(existing => existing.name !== guiId);\n }\n }\n\n private resolveComponent(gui: GuiOptions | any) {\n return gui?.component ?? gui;\n }\n\n private resolveGuiId(gui: GuiOptions | any, component: any) {\n return gui?.name || gui?.id || component?.name || component?.__name;\n }\n\n private resolveAttachToSprite(gui: GuiOptions | any, component: any) {\n return !!(gui?.attachToSprite || gui?.rpgAttachToSprite || component?.attachToSprite || component?.rpgAttachToSprite);\n }\n\n private toGuiState(gui: GuiInstance, display = gui.display(), data = gui.data()): GuiState {\n return {\n name: gui.name,\n component: gui.component,\n display,\n data,\n openId: gui.openId,\n attachToSprite: gui.attachToSprite || false,\n };\n }\n\n private clearPendingActions(guiId: string) {\n this.pendingActions.delete(guiId);\n }\n\n private applyReducers(guiId: string, data: any, actions: GuiAction[]) {\n const reducers = this.optimisticReducers.get(guiId);\n if (!reducers || reducers.length === 0) return data;\n let next = data;\n for (const action of actions) {\n for (const reducer of reducers) {\n const updated = reducer(next, action);\n if (updated !== undefined && updated !== null && updated !== next) {\n next = updated;\n }\n }\n }\n return next;\n }\n\n private applyOptimisticAction(action: GuiAction) {\n const guiInstance = this.get(action.guiId);\n if (!guiInstance) return;\n const reducers = this.optimisticReducers.get(action.guiId);\n if (!reducers || reducers.length === 0) return;\n const currentData = guiInstance.data();\n const nextData = this.applyReducers(action.guiId, currentData, [action]);\n if (nextData === currentData) return;\n guiInstance.data.set(nextData);\n const pending = this.pendingActions.get(action.guiId) || [];\n pending.push(action);\n this.pendingActions.set(action.guiId, pending);\n if (this.isVueComponent(action.guiId)) {\n this._notifyVueGui(action.guiId, guiInstance.display(), nextData);\n }\n }\n\n private applyServerUpdate(guiId: string, data: any, clientActionId?: string) {\n const guiInstance = this.get(guiId);\n if (!guiInstance) return;\n let pending = this.pendingActions.get(guiId) || [];\n if (clientActionId) {\n pending = pending.filter(action => action.clientActionId !== clientActionId);\n } else {\n pending = [];\n }\n let nextData = data;\n if (pending.length) {\n nextData = this.applyReducers(guiId, nextData, pending);\n }\n guiInstance.data.set(nextData);\n this.pendingActions.set(guiId, pending);\n if (this.isVueComponent(guiId)) {\n this._notifyVueGui(guiId, guiInstance.display(), nextData);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Gui.js","names":[],"sources":["../../src/Gui/Gui.ts"],"sourcesContent":["import { Context, inject } from \"@signe/di\";\nimport { signal, Signal, WritableSignal } from \"canvasengine\";\nimport { AbstractWebsocket, WebSocketToken } from \"../services/AbstractSocket\";\nimport { DialogboxComponent, ShopComponent, SaveLoadComponent, MainMenuComponent, NotificationComponent, TitleScreenComponent, GameoverComponent, InputComponent } from \"../components/gui\";\nimport { combineLatest, Subscription } from \"rxjs\";\nimport { PrebuiltGui } from \"@rpgjs/common\";\n\ninterface GuiOptions {\n name?: string;\n id?: string;\n component?: any;\n display?: boolean;\n data?: any;\n /**\n * Auto display the GUI when added to the system\n * @default false\n */\n autoDisplay?: boolean;\n /**\n * Function that returns an array of Signal dependencies\n * The GUI will only display when all dependencies are resolved (!= undefined)\n * @returns Array of Signal dependencies\n */\n dependencies?: () => Signal[];\n /**\n * Attach the GUI to sprites instead of displaying globally\n * When true, the GUI will be rendered in character.ce for each sprite\n * @default false\n */\n attachToSprite?: boolean;\n /**\n * Vue v4 compatibility flag. Prefer attachToSprite in v5 projects.\n */\n rpgAttachToSprite?: boolean;\n}\n\nexport interface GuiInstance {\n name: string;\n component: any;\n display: WritableSignal<boolean>;\n data: WritableSignal<any>;\n openId?: string;\n autoDisplay: boolean;\n dependencies?: Signal[];\n subscription?: Subscription;\n attachToSprite?: boolean;\n}\n\ntype GuiState = {\n name: string;\n component: any;\n display: boolean;\n data: any;\n openId?: string;\n attachToSprite: boolean;\n};\n\ntype VueGuiBridge = {\n updateGuiState?: (state: GuiState) => void;\n initializeGuiStates?: (states: GuiState[]) => void;\n};\n\ninterface GuiAction {\n guiId: string;\n name: string;\n data: any;\n clientActionId: string;\n}\n\ntype OptimisticReducer = (data: any, action: GuiAction) => any;\n\nconst throwError = (id: string) => {\n throw `The GUI named ${id} is non-existent. Please add the component in the gui property of the decorator @RpgClient`;\n};\n\nconst updateItemQuantity = (items: any[], id: string) => {\n const index = items.findIndex((item) => item?.id === id);\n if (index === -1) return items;\n const item = items[index];\n if (item?.usable === false) return items;\n if (item?.consumable === false) return items;\n const quantity = typeof item?.quantity === \"number\" ? item.quantity : 1;\n const nextQuantity = Math.max(0, quantity - 1);\n if (nextQuantity === quantity) return items;\n if (nextQuantity <= 0) {\n return items.filter((_, idx) => idx !== index);\n }\n const nextItems = items.slice();\n nextItems[index] = { ...item, quantity: nextQuantity };\n return nextItems;\n};\n\nconst updateEquippedFlag = (items: any[], id: string, equip: boolean) => {\n const index = items.findIndex((item) => item?.id === id);\n if (index === -1) return items;\n const item = items[index];\n if (item?.equipped === equip) return items;\n const nextItems = items.slice();\n nextItems[index] = { ...item, equipped: equip };\n return nextItems;\n};\n\nconst mainMenuOptimisticReducer: OptimisticReducer = (data, action) => {\n if (!data || typeof data !== \"object\") return data;\n if (action.name === \"useItem\") {\n if (!Array.isArray(data.items)) return data;\n const id = action.data?.id;\n if (!id) return data;\n const nextItems = updateItemQuantity(data.items, id);\n if (nextItems === data.items) return data;\n return { ...data, items: nextItems };\n }\n if (action.name === \"equipItem\") {\n const id = action.data?.id;\n if (!id || typeof action.data?.equip !== \"boolean\") return data;\n const equip = action.data.equip;\n let nextItems = data.items;\n let nextEquips = data.equips;\n if (Array.isArray(data.items)) {\n nextItems = updateEquippedFlag(data.items, id, equip);\n }\n if (Array.isArray(data.equips)) {\n nextEquips = updateEquippedFlag(data.equips, id, equip);\n }\n if (nextItems === data.items && nextEquips === data.equips) return data;\n return {\n ...data,\n ...(nextItems !== data.items ? { items: nextItems } : {}),\n ...(nextEquips !== data.equips ? { equips: nextEquips } : {})\n };\n }\n return data;\n};\n\nexport class RpgGui {\n private webSocket: AbstractWebsocket;\n gui = signal<Record<string, GuiInstance>>({});\n extraGuis: GuiInstance[] = [];\n private vueGuiInstance: VueGuiBridge | null = null;\n private optimisticReducers = new Map<string, OptimisticReducer[]>();\n private pendingActions = new Map<string, GuiAction[]>();\n /**\n * Signal tracking which player IDs should display attached GUIs\n * Key: player ID, Value: boolean (true = show, false = hide)\n */\n attachedGuiDisplayState = signal<Record<string, boolean>>({});\n\n constructor(private context: Context) {\n this.webSocket = inject(context, WebSocketToken);\n this.add({\n name: \"rpg-dialog\",\n component: DialogboxComponent,\n });\n this.add({\n name: PrebuiltGui.MainMenu,\n component: MainMenuComponent,\n });\n this.add({\n name: PrebuiltGui.Shop,\n component: ShopComponent,\n });\n this.add({\n name: PrebuiltGui.Notification,\n component: NotificationComponent,\n autoDisplay: true,\n });\n this.add({\n name: PrebuiltGui.Save,\n component: SaveLoadComponent,\n });\n this.add({\n name: PrebuiltGui.TitleScreen,\n component: TitleScreenComponent,\n });\n this.add({\n name: PrebuiltGui.Gameover,\n component: GameoverComponent,\n });\n this.add({\n name: PrebuiltGui.Input,\n component: InputComponent,\n });\n\n this.registerOptimisticReducer(PrebuiltGui.MainMenu, mainMenuOptimisticReducer);\n }\n\n async _initialize() {\n this.webSocket.on(\"gui.open\", (data: { guiId: string; data: any; guiOpenId?: string }) => {\n this.clearPendingActions(data.guiId);\n this.display(data.guiId, data.data, [], data.guiOpenId);\n });\n\n this.webSocket.on(\"gui.exit\", (payload: string | { guiId: string; guiOpenId?: string }) => {\n const guiId = typeof payload === \"string\" ? payload : payload.guiId;\n const guiOpenId = typeof payload === \"string\" ? undefined : payload.guiOpenId;\n const current = this.get(guiId);\n if (guiOpenId && current?.openId && current.openId !== guiOpenId) {\n return;\n }\n this.hide(guiId);\n });\n\n this.webSocket.on(\"gui.update\", (payload: { guiId: string; data: any; clientActionId?: string }) => {\n this.applyServerUpdate(payload.guiId, payload.data, payload.clientActionId);\n });\n\n /**\n * Listen for tooltip display state changes from server\n * This is triggered by showAttachedGui/hideAttachedGui on the server\n */\n this.webSocket.on(\"gui.tooltip\", (data: { players: string[]; display: boolean }) => {\n const currentState = { ...this.attachedGuiDisplayState() };\n data.players.forEach((playerId) => {\n currentState[playerId] = data.display;\n });\n this.attachedGuiDisplayState.set(currentState);\n });\n }\n\n /**\n * Set the VueGui instance reference for Vue component management\n * This is called by VueGui when it's initialized\n * \n * @param vueGuiInstance - The VueGui instance\n */\n _setVueGuiInstance(vueGuiInstance: any) {\n this.vueGuiInstance = vueGuiInstance;\n this._initializeVueComponents();\n }\n\n /**\n * Notify VueGui about GUI state changes\n * This synchronizes the Vue component display state\n * \n * @param guiId - The GUI component ID\n * @param display - Display state\n * @param data - Component data\n */\n private _notifyVueGui(guiId: string, display: boolean, data: any = {}) {\n const extraGui = this.extraGuis.find(gui => gui.name === guiId);\n if (!extraGui) return;\n this.vueGuiInstance?.updateGuiState?.(this.toGuiState(extraGui, display, data));\n }\n\n /**\n * Initialize Vue components in the VueGui instance\n * This should be called after VueGui is mounted\n */\n _initializeVueComponents() {\n this.vueGuiInstance?.initializeGuiStates?.(\n this.extraGuis.map(gui => this.toGuiState(gui))\n );\n }\n\n guiInteraction(guiId: string, name: string, data: any) {\n const clientActionId = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`;\n const actionData = { ...(data || {}), clientActionId };\n this.applyOptimisticAction({\n guiId,\n name,\n data: actionData,\n clientActionId\n });\n this.webSocket.emit(\"gui.interaction\", {\n guiId,\n name,\n data: actionData,\n });\n }\n\n guiClose(guiId: string, data?: any, guiOpenId?: unknown) {\n const normalizedOpenId =\n typeof guiOpenId === \"string\" && guiOpenId.length > 0 ? guiOpenId : undefined;\n this.webSocket.emit(\"gui.exit\", {\n guiId,\n guiOpenId: normalizedOpenId,\n data,\n });\n }\n\n /**\n * Add a GUI component to the system\n * \n * By default, only CanvasEngine components (.ce files) are accepted.\n * Vue components should be handled by the @rpgjs/vue package.\n * \n * @param gui - GUI configuration options\n * @param gui.name - Name or ID of the GUI component\n * @param gui.id - Alternative ID if name is not provided\n * @param gui.component - The component to render (must be a CanvasEngine component)\n * @param gui.display - Initial display state (default: false)\n * @param gui.data - Initial data for the component\n * @param gui.autoDisplay - Auto display when added (default: false)\n * @param gui.dependencies - Function returning Signal dependencies\n * @param gui.attachToSprite - Attach GUI to sprites instead of global display (default: false)\n * \n * @example\n * ```ts\n * gui.add({\n * name: 'inventory',\n * component: InventoryComponent, // Must be a .ce component\n * autoDisplay: true,\n * dependencies: () => [playerSignal, inventorySignal]\n * });\n * \n * // Attach to sprites\n * gui.add({\n * name: 'tooltip',\n * component: TooltipComponent,\n * attachToSprite: true\n * });\n * ```\n */\n add(gui: GuiOptions | any) {\n const component = this.resolveComponent(gui);\n const guiId = this.resolveGuiId(gui, component);\n if (!guiId) {\n throw new Error(\"GUI must have a name or id\");\n }\n const attachToSprite = this.resolveAttachToSprite(gui, component);\n const guiInstance: GuiInstance = {\n name: guiId,\n component,\n display: signal<boolean>(gui.display || false),\n data: signal<any>(gui.data || {}),\n openId: undefined,\n autoDisplay: gui.autoDisplay || false,\n dependencies: gui.dependencies ? gui.dependencies() : [],\n attachToSprite,\n };\n\n if (this.isVueComponentInstance(guiInstance)) {\n this.removeCanvasGui(guiId);\n const existingIndex = this.extraGuis.findIndex(existing => existing.name === guiId);\n if (existingIndex >= 0) {\n this.extraGuis[existingIndex].subscription?.unsubscribe();\n this.extraGuis[existingIndex] = guiInstance;\n } else {\n this.extraGuis.push(guiInstance);\n }\n\n this._initializeVueComponents();\n \n if (guiInstance.autoDisplay) {\n this.display(guiId, gui.data);\n } else {\n this._notifyVueGui(guiId, guiInstance.display(), guiInstance.data());\n }\n return;\n }\n\n this.removeVueGui(guiId);\n this.gui()[guiId] = guiInstance;\n this._initializeVueComponents();\n\n // Auto display if enabled and it's a CanvasEngine component\n if (guiInstance.autoDisplay && typeof gui.component === 'function') {\n this.display(guiId, gui.data);\n }\n }\n\n registerOptimisticReducer(guiId: string, reducer: OptimisticReducer) {\n const existing = this.optimisticReducers.get(guiId) || [];\n this.optimisticReducers.set(guiId, existing.concat(reducer));\n }\n\n /**\n * Get all attached GUI components (attachToSprite: true)\n * \n * Returns all GUI instances that are configured to be attached to sprites.\n * These GUIs should be rendered in character.ce instead of canvas.ce.\n * \n * @returns Array of GUI instances with attachToSprite: true\n * \n * @example\n * ```ts\n * const attachedGuis = gui.getAttachedGuis();\n * // Use in character.ce to render tooltips\n * ```\n */\n getAttachedGuis(): GuiInstance[] {\n return Object.values(this.gui()).filter(gui => gui.attachToSprite === true);\n }\n\n getVueGuis(): GuiInstance[] {\n return [...this.extraGuis];\n }\n\n getAttachedVueGuis(): GuiInstance[] {\n return this.extraGuis.filter(gui => gui.attachToSprite === true);\n }\n\n /**\n * Check if a player should display attached GUIs\n * \n * @param playerId - The player ID to check\n * @returns true if attached GUIs should be displayed for this player\n */\n shouldDisplayAttachedGui(playerId: string): boolean {\n return this.attachedGuiDisplayState()[playerId] === true;\n }\n\n get(id: string): GuiInstance | undefined {\n // Check CanvasEngine GUIs first\n const canvasGui = this.gui()[id];\n if (canvasGui) {\n return canvasGui;\n }\n \n // Check Vue GUIs in extraGuis\n return this.extraGuis.find(gui => gui.name === id);\n }\n\n exists(id: string): boolean {\n return !!this.get(id);\n }\n\n getAll(): Record<string, GuiInstance> {\n const allGuis = { ...this.gui() };\n \n // Add extraGuis to the result\n this.extraGuis.forEach(gui => {\n allGuis[gui.name] = gui;\n });\n \n return allGuis;\n }\n\n /**\n * Display a GUI component\n * \n * Displays the GUI immediately if no dependencies are configured,\n * or waits for all dependencies to be resolved if dependencies are present.\n * Automatically manages subscriptions to prevent memory leaks.\n * Works with both CanvasEngine components and Vue components.\n * \n * @param id - The GUI component ID\n * @param data - Data to pass to the component\n * @param dependencies - Optional runtime dependencies (overrides config dependencies)\n * \n * @example\n * ```ts\n * // Display immediately\n * gui.display('inventory', { items: [] });\n * \n * // Display with runtime dependencies\n * gui.display('shop', { shopId: 1 }, [playerSignal, shopSignal]);\n * ```\n */\n display(id: string, data = {}, dependencies: Signal[] = [], openId?: string) {\n if (!this.exists(id)) {\n throw throwError(id);\n }\n\n const guiInstance = this.get(id)!;\n const isVueComponent = this.extraGuis.some(gui => gui.name === id);\n\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n const show = () => {\n guiInstance.openId = openId;\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n if (isVueComponent) {\n this._notifyVueGui(id, true, data);\n }\n };\n\n const deps = dependencies.length > 0\n ? dependencies\n : (guiInstance.dependencies ?? []);\n\n if (deps.length > 0) {\n const values = deps.map(dependency => dependency());\n const subscription = new Subscription();\n const showIfReady = () => {\n if (values.every(value => value !== undefined)) {\n show();\n }\n };\n\n deps.forEach((dependency, index) => {\n subscription.add(dependency.observable.subscribe((value) => {\n values[index] = value;\n showIfReady();\n }));\n });\n\n guiInstance.subscription = subscription;\n showIfReady();\n return;\n }\n\n show();\n }\n\n isDisplaying(id: string): boolean {\n const guiInstance = this.get(id);\n if (!guiInstance) return false;\n return guiInstance.display();\n }\n\n /**\n * Handle Vue component display logic\n * \n * @param id - GUI component ID\n * @param data - Component data\n * @param dependencies - Runtime dependencies\n * @param guiInstance - GUI instance\n */\n private _handleVueComponentDisplay(id: string, data: any, dependencies: Signal[], guiInstance: GuiInstance, openId?: string) {\n // Unsubscribe from previous subscription if exists\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n // Use runtime dependencies or config dependencies\n const deps = dependencies.length > 0 \n ? dependencies \n : (guiInstance.dependencies ?? []);\n\n if (deps.length > 0) {\n const values = deps.map(dependency => dependency());\n const subscription = new Subscription();\n const showIfReady = () => {\n if (values.every(value => value !== undefined)) {\n guiInstance.openId = openId;\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n this._notifyVueGui(id, true, data);\n }\n };\n\n deps.forEach((dependency, index) => {\n subscription.add(dependency.observable.subscribe((value) => {\n values[index] = value;\n showIfReady();\n }));\n });\n\n guiInstance.subscription = subscription;\n showIfReady();\n return;\n }\n\n // No dependencies, display immediately\n guiInstance.openId = openId;\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n this._notifyVueGui(id, true, data);\n }\n\n /**\n * Hide a GUI component\n * \n * Hides the GUI and cleans up any active subscriptions.\n * Works with both CanvasEngine components and Vue components.\n * \n * @param id - The GUI component ID\n * \n * @example\n * ```ts\n * gui.hide('inventory');\n * ```\n */\n hide(id: string) {\n if (!this.exists(id)) {\n throw throwError(id);\n }\n\n const guiInstance = this.get(id)!;\n \n // Unsubscribe if there's an active subscription\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n guiInstance.display.set(false)\n guiInstance.openId = undefined;\n \n // Check if it's a Vue component and notify VueGui\n const isVueComponent = this.extraGuis.some(gui => gui.name === id);\n if (isVueComponent) {\n this._notifyVueGui(id, false);\n }\n }\n\n private isVueComponent(id: string) {\n return this.extraGuis.some(gui => gui.name === id);\n }\n\n private isVueComponentInstance(gui: GuiInstance) {\n return typeof gui.component !== \"function\";\n }\n\n private removeCanvasGui(guiId: string) {\n const current = this.gui();\n if (!(guiId in current)) return;\n const next = { ...current };\n delete next[guiId];\n this.gui.set(next);\n }\n\n private removeVueGui(guiId: string) {\n const removed = this.extraGuis.filter(existing => existing.name === guiId);\n removed.forEach(gui => gui.subscription?.unsubscribe());\n if (removed.length > 0) {\n this.extraGuis = this.extraGuis.filter(existing => existing.name !== guiId);\n }\n }\n\n private resolveComponent(gui: GuiOptions | any) {\n return gui?.component ?? gui;\n }\n\n private resolveGuiId(gui: GuiOptions | any, component: any) {\n return gui?.name || gui?.id || component?.name || component?.__name;\n }\n\n private resolveAttachToSprite(gui: GuiOptions | any, component: any) {\n return !!(gui?.attachToSprite || gui?.rpgAttachToSprite || component?.attachToSprite || component?.rpgAttachToSprite);\n }\n\n private toGuiState(gui: GuiInstance, display = gui.display(), data = gui.data()): GuiState {\n return {\n name: gui.name,\n component: gui.component,\n display,\n data,\n openId: gui.openId,\n attachToSprite: gui.attachToSprite || false,\n };\n }\n\n private clearPendingActions(guiId: string) {\n this.pendingActions.delete(guiId);\n }\n\n private applyReducers(guiId: string, data: any, actions: GuiAction[]) {\n const reducers = this.optimisticReducers.get(guiId);\n if (!reducers || reducers.length === 0) return data;\n let next = data;\n for (const action of actions) {\n for (const reducer of reducers) {\n const updated = reducer(next, action);\n if (updated !== undefined && updated !== null && updated !== next) {\n next = updated;\n }\n }\n }\n return next;\n }\n\n private applyOptimisticAction(action: GuiAction) {\n const guiInstance = this.get(action.guiId);\n if (!guiInstance) return;\n const reducers = this.optimisticReducers.get(action.guiId);\n if (!reducers || reducers.length === 0) return;\n const currentData = guiInstance.data();\n const nextData = this.applyReducers(action.guiId, currentData, [action]);\n if (nextData === currentData) return;\n guiInstance.data.set(nextData);\n const pending = this.pendingActions.get(action.guiId) || [];\n pending.push(action);\n this.pendingActions.set(action.guiId, pending);\n if (this.isVueComponent(action.guiId)) {\n this._notifyVueGui(action.guiId, guiInstance.display(), nextData);\n }\n }\n\n private applyServerUpdate(guiId: string, data: any, clientActionId?: string) {\n const guiInstance = this.get(guiId);\n if (!guiInstance) return;\n let pending = this.pendingActions.get(guiId) || [];\n if (clientActionId) {\n pending = pending.filter(action => action.clientActionId !== clientActionId);\n } else {\n pending = [];\n }\n let nextData = data;\n if (pending.length) {\n nextData = this.applyReducers(guiId, nextData, pending);\n }\n guiInstance.data.set(nextData);\n this.pendingActions.set(guiId, pending);\n if (this.isVueComponent(guiId)) {\n this._notifyVueGui(guiId, guiInstance.display(), nextData);\n }\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
|
*
|