@rpgjs/client 5.0.0-beta.20 → 5.0.0-beta.23
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 +16 -0
- package/dist/Game/Object.d.ts +1 -0
- package/dist/Game/Object.js +10 -0
- package/dist/Game/Object.js.map +1 -1
- package/dist/Gui/Gui.js +37 -4
- package/dist/Gui/Gui.js.map +1 -1
- package/dist/RpgClientEngine.d.ts +18 -9
- package/dist/RpgClientEngine.js +40 -13
- package/dist/RpgClientEngine.js.map +1 -1
- package/dist/components/character-hitbox.d.ts +13 -0
- package/dist/components/character-hitbox.js +38 -0
- package/dist/components/character-hitbox.js.map +1 -0
- package/dist/components/character-hitbox.spec.d.ts +1 -0
- package/dist/components/character.ce.js +179 -36
- package/dist/components/character.ce.js.map +1 -1
- package/dist/components/gui/mobile/index.d.ts +51 -2
- package/dist/components/gui/mobile/index.js +12 -4
- package/dist/components/gui/mobile/index.js.map +1 -1
- package/dist/components/gui/mobile/index.spec.d.ts +1 -0
- package/dist/components/gui/mobile/mobile.ce.js +303 -55
- package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/services/cameraFollow.d.ts +51 -0
- package/dist/services/cameraFollow.js +134 -0
- package/dist/services/cameraFollow.js.map +1 -0
- package/dist/services/cameraFollow.spec.d.ts +1 -0
- package/dist/services/standalone.d.ts +6 -1
- package/dist/services/standalone.js +35 -17
- package/dist/services/standalone.js.map +1 -1
- package/dist/utils/syncHitbox.d.ts +1 -0
- package/dist/utils/syncHitbox.js +69 -0
- package/dist/utils/syncHitbox.js.map +1 -0
- package/dist/utils/syncHitbox.spec.d.ts +1 -0
- package/package.json +5 -5
- package/src/Game/Object.spec.ts +44 -4
- package/src/Game/Object.ts +19 -0
- package/src/Gui/Gui.spec.ts +19 -0
- package/src/Gui/Gui.ts +50 -11
- package/src/RpgClientEngine.ts +56 -24
- package/src/components/character-hitbox.spec.ts +33 -0
- package/src/components/character-hitbox.ts +72 -0
- package/src/components/character.ce +207 -48
- package/src/components/gui/mobile/index.spec.ts +94 -0
- package/src/components/gui/mobile/index.ts +74 -6
- package/src/components/gui/mobile/mobile.ce +347 -65
- package/src/index.ts +12 -0
- package/src/services/cameraFollow.spec.ts +220 -0
- package/src/services/cameraFollow.ts +222 -0
- package/src/services/standalone.spec.ts +47 -0
- package/src/services/standalone.ts +53 -20
- package/src/utils/syncHitbox.spec.ts +79 -0
- package/src/utils/syncHitbox.ts +104 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @rpgjs/client
|
|
2
2
|
|
|
3
|
+
## 5.0.0-beta.23
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e7d8d13: Hydrate Studio event hitboxes from the API initially, apply synchronized hitbox object payloads to client physics bodies, publish runtime Studio hitbox changes through the synced event collection instead of a separate setHitbox websocket path, and keep standalone map transfers on the restored room so transferred player positions and hitboxes are preserved in sample-dev.
|
|
8
|
+
- Updated dependencies [e7d8d13]
|
|
9
|
+
- @rpgjs/common@5.0.0-beta.22
|
|
10
|
+
- @rpgjs/server@5.0.0-beta.23
|
|
11
|
+
|
|
12
|
+
## 5.0.0-beta.22
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 1028c17: Use workspace protocol for internal RPGJS package dependencies during prerelease development so CI installs do not fetch unpublished beta packages from npm.
|
|
17
|
+
- @rpgjs/server@5.0.0-beta.22
|
|
18
|
+
|
|
3
19
|
## 5.0.0-beta.20
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/dist/Game/Object.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ export declare abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
51
51
|
* @returns The active {@link RpgClientEngine} instance.
|
|
52
52
|
*/
|
|
53
53
|
get engine(): RpgClientEngine<unknown>;
|
|
54
|
+
setHitbox(width: number, height: number): void;
|
|
54
55
|
private animationSubscription?;
|
|
55
56
|
private animationResetTimeout?;
|
|
56
57
|
private animationWaitResolve?;
|
package/dist/Game/Object.js
CHANGED
|
@@ -66,6 +66,16 @@ var RpgClientObject = class extends RpgCommonPlayer {
|
|
|
66
66
|
get engine() {
|
|
67
67
|
return inject(RpgClientEngine);
|
|
68
68
|
}
|
|
69
|
+
setHitbox(width, height) {
|
|
70
|
+
if (typeof width !== "number" || width <= 0) throw new Error("setHitbox: width must be a positive number");
|
|
71
|
+
if (typeof height !== "number" || height <= 0) throw new Error("setHitbox: height must be a positive number");
|
|
72
|
+
this.hitbox.set({
|
|
73
|
+
w: width,
|
|
74
|
+
h: height
|
|
75
|
+
});
|
|
76
|
+
const sceneMap = this.engine?.sceneMap;
|
|
77
|
+
if (sceneMap && this.id) sceneMap.updateHitbox?.(this.id, this.x(), this.y(), width, height);
|
|
78
|
+
}
|
|
69
79
|
clearAnimationControls() {
|
|
70
80
|
if (this.animationSubscription) {
|
|
71
81
|
this.animationSubscription.unsubscribe();
|
package/dist/Game/Object.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Object.js","names":[],"sources":["../../src/Game/Object.ts"],"sourcesContent":["import { Hooks, ModulesToken, RpgCommonPlayer } from \"@rpgjs/common\";\nimport { trigger, signal, type Trigger } from \"canvasengine\";\nimport { combineLatest, from, map, of, startWith, Subscription, switchMap } from \"rxjs\";\nimport { inject } from \"../core/inject\";\nimport { RpgClientEngine } from \"../RpgClientEngine\";\ntype Frame = { x: number; y: number; ts: number };\n\ntype AnimationRestoreOptions = {\n restoreAnimationName?: string;\n restoreGraphics?: any[];\n timeoutMs?: number;\n};\n\ntype FlashType = 'alpha' | 'tint' | 'both';\n\ntype FlashOptions = {\n type?: FlashType;\n duration?: number;\n cycles?: number;\n alpha?: number;\n tint?: number | string;\n};\n\ntype FlashTriggerOptions = Omit<FlashOptions, \"tint\"> & {\n tint: number;\n};\n\ntype ConfigurableTrigger<T> = Omit<Trigger<T>, \"start\"> & {\n start(config?: T): Promise<void>;\n};\n\nexport const withGraphicDisplayScale = (spritesheet: any, scale: unknown): any => {\n if (!spritesheet || typeof spritesheet !== \"object\") return spritesheet;\n if (scale === undefined || scale === null) return spritesheet;\n return {\n ...spritesheet,\n displayScale: scale,\n };\n};\n\nexport const appendFramePayload = (current: unknown, items: unknown): Frame[] => {\n const frameItems = Array.isArray(items) ? items : items ? [items] : [];\n const nextFrames = frameItems.flatMap((item): Frame[] =>\n Array.isArray(item) ? item : [item as Frame]\n );\n const currentFrames = Array.isArray(current) ? current as Frame[] : [];\n return currentFrames.concat(nextFrames);\n};\n\nexport abstract class RpgClientObject extends RpgCommonPlayer {\n abstract _type: string;\n emitParticleTrigger = trigger();\n particleName = signal(\"\");\n animationCurrentIndex = signal(0);\n animationIsPlaying = signal(false);\n _param = signal({});\n frames: Frame[] = [];\n graphicsSignals = signal<any[]>([]);\n flashTrigger: ConfigurableTrigger<FlashTriggerOptions> = trigger<FlashTriggerOptions>();\n private animationRestoreState?: {\n animationName: string;\n graphics: any[];\n };\n\n constructor() {\n super();\n const engine = this.engine;\n this.hooks.callHooks(\"client-sprite-onInit\", this).subscribe();\n\n this._frames.observable.subscribe(({ items }) => {\n if (!this.id) return;\n //if (this.id == this.engine.playerIdSignal()!) return;\n this.frames = appendFramePayload(this.frames, items);\n });\n\n const graphics$ = this.graphics.observable.pipe(map(({ items }) => items));\n const graphicScale$ = this._graphicScale.observable.pipe(\n startWith({ value: this._graphicScale() }),\n map((payload: any) => payload?.value ?? payload),\n );\n\n combineLatest([graphics$, graphicScale$])\n .pipe(\n switchMap(([graphics, scale]) => {\n const graphicRefs = Array.isArray(graphics) ? graphics : [];\n if (graphicRefs.length === 0) return of([]);\n return from(Promise.all(graphicRefs.map(async (graphic) => {\n const spritesheet = await engine.getSpriteSheet(graphic);\n return withGraphicDisplayScale(spritesheet, scale);\n })));\n })\n )\n .subscribe((sheets) => { \n this.graphicsSignals.set(sheets);\n });\n\n engine.tick\n .pipe\n //throttleTime(10)\n ()\n .subscribe(() => {\n const frame = this.frames.shift();\n if (frame) {\n if (typeof frame.x !== \"number\" || typeof frame.y !== \"number\") return;\n engine.scene.setBodyPosition(\n this.id,\n frame.x,\n frame.y,\n \"top-left\"\n );\n }\n });\n }\n\n /**\n * Access the shared client hook registry.\n *\n * @returns The hook service used to register and trigger client-side hooks.\n */\n get hooks() {\n return inject<Hooks>(ModulesToken);\n }\n\n /**\n * Access the current client engine instance.\n *\n * @returns The active {@link RpgClientEngine} instance.\n */\n get engine() {\n return inject(RpgClientEngine);\n }\n\n private animationSubscription?: Subscription;\n private animationResetTimeout?: ReturnType<typeof setTimeout>;\n private animationWaitResolve?: () => void;\n\n private clearAnimationControls() {\n if (this.animationSubscription) {\n this.animationSubscription.unsubscribe();\n this.animationSubscription = undefined;\n }\n if (this.animationResetTimeout) {\n clearTimeout(this.animationResetTimeout);\n this.animationResetTimeout = undefined;\n }\n }\n\n private resolveAnimationWait() {\n const resolve = this.animationWaitResolve;\n this.animationWaitResolve = undefined;\n resolve?.();\n }\n\n private finishTemporaryAnimation() {\n const restoreState = this.animationRestoreState;\n this.clearAnimationControls();\n this.animationCurrentIndex.set(0);\n this.animationRestoreState = undefined;\n this.animationIsPlaying.set(false);\n if (restoreState) {\n this.animationName.set(restoreState.animationName);\n this.graphics.set([...restoreState.graphics]);\n }\n this.resolveAnimationWait();\n }\n\n /**\n * Trigger a flash animation on this sprite\n * \n * This method triggers a flash effect using CanvasEngine's flash directive.\n * The flash can be configured with various options including type (alpha, tint, or both),\n * duration, cycles, and color.\n * \n * ## Design\n * \n * The flash uses a trigger system that is connected to the flash directive in the\n * character component. This allows for flexible configuration and can be triggered\n * from both server events and client-side code.\n * \n * @param options - Flash configuration options\n * @param options.type - Type of flash effect: 'alpha' (opacity), 'tint' (color), or 'both' (default: 'alpha')\n * @param options.duration - Duration of the flash animation in milliseconds (default: 300)\n * @param options.cycles - Number of flash cycles (flash on/off) (default: 1)\n * @param options.alpha - Alpha value when flashing, from 0 to 1 (default: 0.3)\n * @param options.tint - Tint color when flashing as hex value or color name (default: 0xffffff - white)\n * \n * @example\n * ```ts\n * // Simple flash with default settings (alpha flash)\n * player.flash();\n * \n * // Flash with red tint\n * player.flash({ type: 'tint', tint: 0xff0000 });\n * \n * // Flash with both alpha and tint\n * player.flash({ \n * type: 'both', \n * alpha: 0.5, \n * tint: 0xff0000,\n * duration: 200,\n * cycles: 2\n * });\n * \n * // Quick damage flash\n * player.flash({ \n * type: 'tint', \n * tint: 0xff0000, \n * duration: 150,\n * cycles: 1\n * });\n * ```\n */\n flash(options?: FlashOptions): void {\n const flashOptions = {\n type: options?.type || 'alpha',\n duration: options?.duration ?? 300,\n cycles: options?.cycles ?? 1,\n alpha: options?.alpha ?? 0.3,\n tint: options?.tint ?? 0xffffff,\n };\n \n // Convert color name to hex if needed\n let tintValue = flashOptions.tint;\n if (typeof tintValue === 'string') {\n // Common color name to hex mapping\n const colorMap: Record<string, number> = {\n 'white': 0xffffff,\n 'red': 0xff0000,\n 'green': 0x00ff00,\n 'blue': 0x0000ff,\n 'yellow': 0xffff00,\n 'cyan': 0x00ffff,\n 'magenta': 0xff00ff,\n 'black': 0x000000,\n };\n tintValue = colorMap[tintValue.toLowerCase()] ?? 0xffffff;\n }\n \n this.flashTrigger.start({\n ...flashOptions,\n tint: tintValue,\n });\n }\n\n /**\n * Reset animation state when animation changes externally\n *\n * This method should be called when the animation changes due to movement\n * or other external factors to ensure the animation system doesn't get stuck\n *\n * @example\n * ```ts\n * // Reset when player starts moving\n * player.resetAnimationState();\n * ```\n */\n resetAnimationState() {\n if (this.animationRestoreState) {\n this.finishTemporaryAnimation();\n return;\n }\n this.animationIsPlaying.set(false);\n this.animationCurrentIndex.set(0);\n this.clearAnimationControls();\n this.resolveAnimationWait();\n }\n\n /**\n * Set a custom animation for a specific number of times\n *\n * Plays a custom animation for the specified number of repetitions.\n * The animation system prevents overlapping animations and automatically\n * returns to the previous animation when complete.\n *\n * @param animationName - Name of the animation to play\n * @param nbTimes - Number of times to repeat the animation (default: Infinity for continuous)\n * @param options - Restore and timeout options\n * @returns A promise resolved when a finite animation finishes, is interrupted, or times out\n *\n * @example\n * ```ts\n * // Play attack animation 3 times\n * await player.setAnimation('attack', 3);\n *\n * // Play continuous spell animation\n * player.setAnimation('spell');\n * ```\n */\n setAnimation(animationName: string, nbTimes?: number, options?: AnimationRestoreOptions): Promise<void>;\n /**\n * Set a custom animation with temporary graphic change\n *\n * Plays a custom animation for the specified number of repetitions and temporarily\n * changes the player's graphic (sprite sheet) during the animation. The graphic\n * is automatically reset when the animation finishes.\n *\n * @param animationName - Name of the animation to play\n * @param graphic - The graphic(s) to temporarily use during the animation\n * @param nbTimes - Number of times to repeat the animation (default: Infinity for continuous)\n * @param options - Restore and timeout options\n * @returns A promise resolved when a finite animation finishes, is interrupted, or times out\n *\n * @example\n * ```ts\n * // Play attack animation with temporary graphic change\n * await player.setAnimation('attack', 'hero_attack', 3);\n * ```\n */\n setAnimation(animationName: string, graphic?: string | string[], nbTimes?: number, options?: AnimationRestoreOptions): Promise<void>;\n setAnimation(\n animationName: string,\n graphicOrNbTimes?: string | string[] | number,\n nbTimesOrOptions?: number | AnimationRestoreOptions,\n options?: AnimationRestoreOptions\n ): Promise<void> {\n let graphic: string | string[] | undefined;\n let finalNbTimes: number = Infinity;\n let restoreOptions: AnimationRestoreOptions | undefined = options;\n\n // Handle overloads\n if (typeof graphicOrNbTimes === 'number') {\n // setAnimation(animationName, nbTimes)\n finalNbTimes = graphicOrNbTimes;\n restoreOptions = typeof nbTimesOrOptions === 'object' ? nbTimesOrOptions : options;\n } else if (graphicOrNbTimes !== undefined) {\n // setAnimation(animationName, graphic, nbTimes)\n graphic = graphicOrNbTimes;\n if (typeof nbTimesOrOptions === 'number') {\n finalNbTimes = nbTimesOrOptions;\n } else {\n finalNbTimes = Infinity;\n restoreOptions = nbTimesOrOptions ?? options;\n }\n } else {\n // setAnimation(animationName) - nbTimes remains Infinity\n finalNbTimes = Infinity;\n }\n\n if (this.animationIsPlaying()) {\n this.finishTemporaryAnimation();\n }\n\n const waitPromise =\n finalNbTimes === Infinity\n ? Promise.resolve()\n : new Promise<void>((resolve) => {\n this.animationWaitResolve = resolve;\n });\n\n this.animationIsPlaying.set(true);\n const previousAnimationName =\n restoreOptions?.restoreAnimationName ?? this.animationName();\n const previousGraphics = restoreOptions?.restoreGraphics\n ? [...restoreOptions.restoreGraphics]\n : [...this.graphics()];\n this.animationRestoreState = {\n animationName: previousAnimationName,\n graphics: previousGraphics,\n };\n this.animationCurrentIndex.set(0);\n\n // Temporarily change graphic if provided\n if (graphic !== undefined) {\n if (Array.isArray(graphic)) {\n this.graphics.set(graphic);\n } else {\n this.graphics.set([graphic]);\n }\n }\n\n this.clearAnimationControls();\n\n this.animationSubscription =\n this.animationCurrentIndex.observable.subscribe((index) => {\n if (index >= finalNbTimes) {\n this.finishTemporaryAnimation();\n }\n });\n\n if (finalNbTimes !== Infinity) {\n this.animationResetTimeout = setTimeout(() => {\n if (this.animationIsPlaying()) {\n this.finishTemporaryAnimation();\n }\n }, restoreOptions?.timeoutMs ?? Math.max(1000, finalNbTimes * 1000));\n }\n\n this.animationName.set(animationName);\n\n return waitPromise;\n }\n\n /**\n * Display a registered component animation effect on this object.\n *\n * @param id - Identifier of the component animation to play.\n * @param params - Parameters forwarded to the animation effect.\n * @returns A promise resolved when the animation component calls `onFinish`.\n */\n showComponentAnimation(id: string, params: any): Promise<void> {\n const engine = inject(RpgClientEngine);\n return engine.getComponentAnimation(id).displayEffect(params, this);\n }\n\n /**\n * Display a registered spritesheet animation effect on this object.\n *\n * @param graphic - Identifier of the spritesheet to use.\n * @param animationName - Name of the animation inside the spritesheet.\n * @returns A promise resolved when the animation component calls `onFinish`.\n */\n showAnimation(graphic: string, animationName: string = 'default'): Promise<void> {\n return this.showComponentAnimation('animation', {\n graphic,\n animationName,\n });\n }\n \n /**\n * Check whether this client object represents an event.\n *\n * @returns `true` if the object type is `event`, otherwise `false`.\n */\n isEvent(): boolean {\n return this._type === 'event';\n }\n\n /**\n * Check whether this client object represents a player.\n *\n * @returns `true` if the object type is `player`, otherwise `false`.\n */\n isPlayer(): boolean {\n return this._type === 'player';\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Object.js","names":[],"sources":["../../src/Game/Object.ts"],"sourcesContent":["import { Hooks, ModulesToken, RpgCommonPlayer } from \"@rpgjs/common\";\nimport { trigger, signal, type Trigger } from \"canvasengine\";\nimport { combineLatest, from, map, of, startWith, Subscription, switchMap } from \"rxjs\";\nimport { inject } from \"../core/inject\";\nimport { RpgClientEngine } from \"../RpgClientEngine\";\ntype Frame = { x: number; y: number; ts: number };\n\ntype AnimationRestoreOptions = {\n restoreAnimationName?: string;\n restoreGraphics?: any[];\n timeoutMs?: number;\n};\n\ntype FlashType = 'alpha' | 'tint' | 'both';\n\ntype FlashOptions = {\n type?: FlashType;\n duration?: number;\n cycles?: number;\n alpha?: number;\n tint?: number | string;\n};\n\ntype FlashTriggerOptions = Omit<FlashOptions, \"tint\"> & {\n tint: number;\n};\n\ntype ConfigurableTrigger<T> = Omit<Trigger<T>, \"start\"> & {\n start(config?: T): Promise<void>;\n};\n\nexport const withGraphicDisplayScale = (spritesheet: any, scale: unknown): any => {\n if (!spritesheet || typeof spritesheet !== \"object\") return spritesheet;\n if (scale === undefined || scale === null) return spritesheet;\n return {\n ...spritesheet,\n displayScale: scale,\n };\n};\n\nexport const appendFramePayload = (current: unknown, items: unknown): Frame[] => {\n const frameItems = Array.isArray(items) ? items : items ? [items] : [];\n const nextFrames = frameItems.flatMap((item): Frame[] =>\n Array.isArray(item) ? item : [item as Frame]\n );\n const currentFrames = Array.isArray(current) ? current as Frame[] : [];\n return currentFrames.concat(nextFrames);\n};\n\nexport abstract class RpgClientObject extends RpgCommonPlayer {\n abstract _type: string;\n emitParticleTrigger = trigger();\n particleName = signal(\"\");\n animationCurrentIndex = signal(0);\n animationIsPlaying = signal(false);\n _param = signal({});\n frames: Frame[] = [];\n graphicsSignals = signal<any[]>([]);\n flashTrigger: ConfigurableTrigger<FlashTriggerOptions> = trigger<FlashTriggerOptions>();\n private animationRestoreState?: {\n animationName: string;\n graphics: any[];\n };\n\n constructor() {\n super();\n const engine = this.engine;\n this.hooks.callHooks(\"client-sprite-onInit\", this).subscribe();\n\n this._frames.observable.subscribe(({ items }) => {\n if (!this.id) return;\n //if (this.id == this.engine.playerIdSignal()!) return;\n this.frames = appendFramePayload(this.frames, items);\n });\n\n const graphics$ = this.graphics.observable.pipe(map(({ items }) => items));\n const graphicScale$ = this._graphicScale.observable.pipe(\n startWith({ value: this._graphicScale() }),\n map((payload: any) => payload?.value ?? payload),\n );\n\n combineLatest([graphics$, graphicScale$])\n .pipe(\n switchMap(([graphics, scale]) => {\n const graphicRefs = Array.isArray(graphics) ? graphics : [];\n if (graphicRefs.length === 0) return of([]);\n return from(Promise.all(graphicRefs.map(async (graphic) => {\n const spritesheet = await engine.getSpriteSheet(graphic);\n return withGraphicDisplayScale(spritesheet, scale);\n })));\n })\n )\n .subscribe((sheets) => { \n this.graphicsSignals.set(sheets);\n });\n\n engine.tick\n .pipe\n //throttleTime(10)\n ()\n .subscribe(() => {\n const frame = this.frames.shift();\n if (frame) {\n if (typeof frame.x !== \"number\" || typeof frame.y !== \"number\") return;\n engine.scene.setBodyPosition(\n this.id,\n frame.x,\n frame.y,\n \"top-left\"\n );\n }\n });\n }\n\n /**\n * Access the shared client hook registry.\n *\n * @returns The hook service used to register and trigger client-side hooks.\n */\n get hooks() {\n return inject<Hooks>(ModulesToken);\n }\n\n /**\n * Access the current client engine instance.\n *\n * @returns The active {@link RpgClientEngine} instance.\n */\n get engine() {\n return inject(RpgClientEngine);\n }\n\n setHitbox(width: number, height: number): void {\n if (typeof width !== \"number\" || width <= 0) {\n throw new Error(\"setHitbox: width must be a positive number\");\n }\n if (typeof height !== \"number\" || height <= 0) {\n throw new Error(\"setHitbox: height must be a positive number\");\n }\n\n this.hitbox.set({\n w: width,\n h: height,\n });\n\n const sceneMap = (this.engine as any)?.sceneMap;\n if (sceneMap && this.id) {\n sceneMap.updateHitbox?.(this.id, this.x(), this.y(), width, height);\n }\n }\n\n private animationSubscription?: Subscription;\n private animationResetTimeout?: ReturnType<typeof setTimeout>;\n private animationWaitResolve?: () => void;\n\n private clearAnimationControls() {\n if (this.animationSubscription) {\n this.animationSubscription.unsubscribe();\n this.animationSubscription = undefined;\n }\n if (this.animationResetTimeout) {\n clearTimeout(this.animationResetTimeout);\n this.animationResetTimeout = undefined;\n }\n }\n\n private resolveAnimationWait() {\n const resolve = this.animationWaitResolve;\n this.animationWaitResolve = undefined;\n resolve?.();\n }\n\n private finishTemporaryAnimation() {\n const restoreState = this.animationRestoreState;\n this.clearAnimationControls();\n this.animationCurrentIndex.set(0);\n this.animationRestoreState = undefined;\n this.animationIsPlaying.set(false);\n if (restoreState) {\n this.animationName.set(restoreState.animationName);\n this.graphics.set([...restoreState.graphics]);\n }\n this.resolveAnimationWait();\n }\n\n /**\n * Trigger a flash animation on this sprite\n * \n * This method triggers a flash effect using CanvasEngine's flash directive.\n * The flash can be configured with various options including type (alpha, tint, or both),\n * duration, cycles, and color.\n * \n * ## Design\n * \n * The flash uses a trigger system that is connected to the flash directive in the\n * character component. This allows for flexible configuration and can be triggered\n * from both server events and client-side code.\n * \n * @param options - Flash configuration options\n * @param options.type - Type of flash effect: 'alpha' (opacity), 'tint' (color), or 'both' (default: 'alpha')\n * @param options.duration - Duration of the flash animation in milliseconds (default: 300)\n * @param options.cycles - Number of flash cycles (flash on/off) (default: 1)\n * @param options.alpha - Alpha value when flashing, from 0 to 1 (default: 0.3)\n * @param options.tint - Tint color when flashing as hex value or color name (default: 0xffffff - white)\n * \n * @example\n * ```ts\n * // Simple flash with default settings (alpha flash)\n * player.flash();\n * \n * // Flash with red tint\n * player.flash({ type: 'tint', tint: 0xff0000 });\n * \n * // Flash with both alpha and tint\n * player.flash({ \n * type: 'both', \n * alpha: 0.5, \n * tint: 0xff0000,\n * duration: 200,\n * cycles: 2\n * });\n * \n * // Quick damage flash\n * player.flash({ \n * type: 'tint', \n * tint: 0xff0000, \n * duration: 150,\n * cycles: 1\n * });\n * ```\n */\n flash(options?: FlashOptions): void {\n const flashOptions = {\n type: options?.type || 'alpha',\n duration: options?.duration ?? 300,\n cycles: options?.cycles ?? 1,\n alpha: options?.alpha ?? 0.3,\n tint: options?.tint ?? 0xffffff,\n };\n \n // Convert color name to hex if needed\n let tintValue = flashOptions.tint;\n if (typeof tintValue === 'string') {\n // Common color name to hex mapping\n const colorMap: Record<string, number> = {\n 'white': 0xffffff,\n 'red': 0xff0000,\n 'green': 0x00ff00,\n 'blue': 0x0000ff,\n 'yellow': 0xffff00,\n 'cyan': 0x00ffff,\n 'magenta': 0xff00ff,\n 'black': 0x000000,\n };\n tintValue = colorMap[tintValue.toLowerCase()] ?? 0xffffff;\n }\n \n this.flashTrigger.start({\n ...flashOptions,\n tint: tintValue,\n });\n }\n\n /**\n * Reset animation state when animation changes externally\n *\n * This method should be called when the animation changes due to movement\n * or other external factors to ensure the animation system doesn't get stuck\n *\n * @example\n * ```ts\n * // Reset when player starts moving\n * player.resetAnimationState();\n * ```\n */\n resetAnimationState() {\n if (this.animationRestoreState) {\n this.finishTemporaryAnimation();\n return;\n }\n this.animationIsPlaying.set(false);\n this.animationCurrentIndex.set(0);\n this.clearAnimationControls();\n this.resolveAnimationWait();\n }\n\n /**\n * Set a custom animation for a specific number of times\n *\n * Plays a custom animation for the specified number of repetitions.\n * The animation system prevents overlapping animations and automatically\n * returns to the previous animation when complete.\n *\n * @param animationName - Name of the animation to play\n * @param nbTimes - Number of times to repeat the animation (default: Infinity for continuous)\n * @param options - Restore and timeout options\n * @returns A promise resolved when a finite animation finishes, is interrupted, or times out\n *\n * @example\n * ```ts\n * // Play attack animation 3 times\n * await player.setAnimation('attack', 3);\n *\n * // Play continuous spell animation\n * player.setAnimation('spell');\n * ```\n */\n setAnimation(animationName: string, nbTimes?: number, options?: AnimationRestoreOptions): Promise<void>;\n /**\n * Set a custom animation with temporary graphic change\n *\n * Plays a custom animation for the specified number of repetitions and temporarily\n * changes the player's graphic (sprite sheet) during the animation. The graphic\n * is automatically reset when the animation finishes.\n *\n * @param animationName - Name of the animation to play\n * @param graphic - The graphic(s) to temporarily use during the animation\n * @param nbTimes - Number of times to repeat the animation (default: Infinity for continuous)\n * @param options - Restore and timeout options\n * @returns A promise resolved when a finite animation finishes, is interrupted, or times out\n *\n * @example\n * ```ts\n * // Play attack animation with temporary graphic change\n * await player.setAnimation('attack', 'hero_attack', 3);\n * ```\n */\n setAnimation(animationName: string, graphic?: string | string[], nbTimes?: number, options?: AnimationRestoreOptions): Promise<void>;\n setAnimation(\n animationName: string,\n graphicOrNbTimes?: string | string[] | number,\n nbTimesOrOptions?: number | AnimationRestoreOptions,\n options?: AnimationRestoreOptions\n ): Promise<void> {\n let graphic: string | string[] | undefined;\n let finalNbTimes: number = Infinity;\n let restoreOptions: AnimationRestoreOptions | undefined = options;\n\n // Handle overloads\n if (typeof graphicOrNbTimes === 'number') {\n // setAnimation(animationName, nbTimes)\n finalNbTimes = graphicOrNbTimes;\n restoreOptions = typeof nbTimesOrOptions === 'object' ? nbTimesOrOptions : options;\n } else if (graphicOrNbTimes !== undefined) {\n // setAnimation(animationName, graphic, nbTimes)\n graphic = graphicOrNbTimes;\n if (typeof nbTimesOrOptions === 'number') {\n finalNbTimes = nbTimesOrOptions;\n } else {\n finalNbTimes = Infinity;\n restoreOptions = nbTimesOrOptions ?? options;\n }\n } else {\n // setAnimation(animationName) - nbTimes remains Infinity\n finalNbTimes = Infinity;\n }\n\n if (this.animationIsPlaying()) {\n this.finishTemporaryAnimation();\n }\n\n const waitPromise =\n finalNbTimes === Infinity\n ? Promise.resolve()\n : new Promise<void>((resolve) => {\n this.animationWaitResolve = resolve;\n });\n\n this.animationIsPlaying.set(true);\n const previousAnimationName =\n restoreOptions?.restoreAnimationName ?? this.animationName();\n const previousGraphics = restoreOptions?.restoreGraphics\n ? [...restoreOptions.restoreGraphics]\n : [...this.graphics()];\n this.animationRestoreState = {\n animationName: previousAnimationName,\n graphics: previousGraphics,\n };\n this.animationCurrentIndex.set(0);\n\n // Temporarily change graphic if provided\n if (graphic !== undefined) {\n if (Array.isArray(graphic)) {\n this.graphics.set(graphic);\n } else {\n this.graphics.set([graphic]);\n }\n }\n\n this.clearAnimationControls();\n\n this.animationSubscription =\n this.animationCurrentIndex.observable.subscribe((index) => {\n if (index >= finalNbTimes) {\n this.finishTemporaryAnimation();\n }\n });\n\n if (finalNbTimes !== Infinity) {\n this.animationResetTimeout = setTimeout(() => {\n if (this.animationIsPlaying()) {\n this.finishTemporaryAnimation();\n }\n }, restoreOptions?.timeoutMs ?? Math.max(1000, finalNbTimes * 1000));\n }\n\n this.animationName.set(animationName);\n\n return waitPromise;\n }\n\n /**\n * Display a registered component animation effect on this object.\n *\n * @param id - Identifier of the component animation to play.\n * @param params - Parameters forwarded to the animation effect.\n * @returns A promise resolved when the animation component calls `onFinish`.\n */\n showComponentAnimation(id: string, params: any): Promise<void> {\n const engine = inject(RpgClientEngine);\n return engine.getComponentAnimation(id).displayEffect(params, this);\n }\n\n /**\n * Display a registered spritesheet animation effect on this object.\n *\n * @param graphic - Identifier of the spritesheet to use.\n * @param animationName - Name of the animation inside the spritesheet.\n * @returns A promise resolved when the animation component calls `onFinish`.\n */\n showAnimation(graphic: string, animationName: string = 'default'): Promise<void> {\n return this.showComponentAnimation('animation', {\n graphic,\n animationName,\n });\n }\n \n /**\n * Check whether this client object represents an event.\n *\n * @returns `true` if the object type is `event`, otherwise `false`.\n */\n isEvent(): boolean {\n return this._type === 'event';\n }\n\n /**\n * Check whether this client object represents a player.\n *\n * @returns `true` if the object type is `player`, otherwise `false`.\n */\n isPlayer(): boolean {\n return this._type === 'player';\n }\n}\n"],"mappings":"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"}
|
package/dist/Gui/Gui.js
CHANGED
|
@@ -10,7 +10,7 @@ import __ce_component$6 from "../components/gui/gameover.ce.js";
|
|
|
10
10
|
import "../components/gui/index.js";
|
|
11
11
|
import { signal } from "canvasengine";
|
|
12
12
|
import { PrebuiltGui } from "@rpgjs/common";
|
|
13
|
-
import {
|
|
13
|
+
import { Subscription } from "rxjs";
|
|
14
14
|
//#region src/Gui/Gui.ts
|
|
15
15
|
var throwError = (id) => {
|
|
16
16
|
throw `The GUI named ${id} is non-existent. Please add the component in the gui property of the decorator @RpgClient`;
|
|
@@ -338,12 +338,35 @@ var RpgGui = class {
|
|
|
338
338
|
display(id, data = {}, dependencies = [], openId) {
|
|
339
339
|
if (!this.exists(id)) throw throwError(id);
|
|
340
340
|
const guiInstance = this.get(id);
|
|
341
|
-
|
|
342
|
-
|
|
341
|
+
const isVueComponent = this.extraGuis.some((gui) => gui.name === id);
|
|
342
|
+
if (guiInstance.subscription) {
|
|
343
|
+
guiInstance.subscription.unsubscribe();
|
|
344
|
+
guiInstance.subscription = void 0;
|
|
345
|
+
}
|
|
346
|
+
const show = () => {
|
|
343
347
|
guiInstance.openId = openId;
|
|
344
348
|
guiInstance.data.set(data);
|
|
345
349
|
guiInstance.display.set(true);
|
|
350
|
+
if (isVueComponent) this._notifyVueGui(id, true, data);
|
|
351
|
+
};
|
|
352
|
+
const deps = dependencies.length > 0 ? dependencies : guiInstance.dependencies ?? [];
|
|
353
|
+
if (deps.length > 0) {
|
|
354
|
+
const values = deps.map((dependency) => dependency());
|
|
355
|
+
const subscription = new Subscription();
|
|
356
|
+
const showIfReady = () => {
|
|
357
|
+
if (values.every((value) => value !== void 0)) show();
|
|
358
|
+
};
|
|
359
|
+
deps.forEach((dependency, index) => {
|
|
360
|
+
subscription.add(dependency.observable.subscribe((value) => {
|
|
361
|
+
values[index] = value;
|
|
362
|
+
showIfReady();
|
|
363
|
+
}));
|
|
364
|
+
});
|
|
365
|
+
guiInstance.subscription = subscription;
|
|
366
|
+
showIfReady();
|
|
367
|
+
return;
|
|
346
368
|
}
|
|
369
|
+
show();
|
|
347
370
|
}
|
|
348
371
|
isDisplaying(id) {
|
|
349
372
|
const guiInstance = this.get(id);
|
|
@@ -365,14 +388,24 @@ var RpgGui = class {
|
|
|
365
388
|
}
|
|
366
389
|
const deps = dependencies.length > 0 ? dependencies : guiInstance.dependencies ?? [];
|
|
367
390
|
if (deps.length > 0) {
|
|
368
|
-
|
|
391
|
+
const values = deps.map((dependency) => dependency());
|
|
392
|
+
const subscription = new Subscription();
|
|
393
|
+
const showIfReady = () => {
|
|
369
394
|
if (values.every((value) => value !== void 0)) {
|
|
370
395
|
guiInstance.openId = openId;
|
|
371
396
|
guiInstance.data.set(data);
|
|
372
397
|
guiInstance.display.set(true);
|
|
373
398
|
this._notifyVueGui(id, true, data);
|
|
374
399
|
}
|
|
400
|
+
};
|
|
401
|
+
deps.forEach((dependency, index) => {
|
|
402
|
+
subscription.add(dependency.observable.subscribe((value) => {
|
|
403
|
+
values[index] = value;
|
|
404
|
+
showIfReady();
|
|
405
|
+
}));
|
|
375
406
|
});
|
|
407
|
+
guiInstance.subscription = subscription;
|
|
408
|
+
showIfReady();
|
|
376
409
|
return;
|
|
377
410
|
}
|
|
378
411
|
guiInstance.openId = openId;
|
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 \n // Check if it's a Vue component (in extraGuis)\n const isVueComponent = this.extraGuis.some(gui => gui.name === id);\n \n if (isVueComponent) {\n // Handle Vue component display\n this._handleVueComponentDisplay(id, data, dependencies, guiInstance, openId);\n } else {\n guiInstance.openId = openId;\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n }\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 // Subscribe to dependencies\n guiInstance.subscription = combineLatest(\n deps.map(dependency => dependency.observable)\n ).subscribe((values) => {\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 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 } 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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"}
|
|
@@ -11,7 +11,9 @@ import { ClientVisualRegistry, ClientVisualHandler, ClientVisualMap, ClientVisua
|
|
|
11
11
|
import { ClientPointerContext } from './services/pointerContext';
|
|
12
12
|
import { RpgClientInteractions } from './services/interactions';
|
|
13
13
|
import { EventComponentResolver } from './Game/EventComponentResolver';
|
|
14
|
+
import { CameraFollowSmoothMove } from './services/cameraFollow';
|
|
14
15
|
import * as PIXI from "pixi.js";
|
|
16
|
+
export type { CameraFollowEase, CameraFollowSmoothMove, CameraFollowSmoothMoveOptions, } from './services/cameraFollow';
|
|
15
17
|
type ConfigurableTrigger<T> = Omit<Trigger<T>, "start"> & {
|
|
16
18
|
start(config?: T): Promise<void>;
|
|
17
19
|
};
|
|
@@ -59,9 +61,13 @@ export declare class RpgClientEngine<T = any> {
|
|
|
59
61
|
private eventComponentResolvers;
|
|
60
62
|
/** ID of the sprite that the camera should follow. null means follow the current player */
|
|
61
63
|
cameraFollowTargetId: import('canvasengine').WritableSignal<string | null>;
|
|
64
|
+
/** Camera follow transition options used by character components when the target changes */
|
|
65
|
+
cameraFollowSmoothMove: CameraFollowSmoothMove;
|
|
66
|
+
/** Incremented for each camera follow command so repeated commands on the same target are applied */
|
|
67
|
+
cameraFollowRevision: import('canvasengine').WritableSignal<number>;
|
|
62
68
|
/** Trigger for map shake animation */
|
|
63
69
|
mapShakeTrigger: ConfigurableTrigger<MapShakeOptions>;
|
|
64
|
-
controlsReady: import('canvasengine').WritableSignal<undefined>;
|
|
70
|
+
controlsReady: import('canvasengine').WritableSignal<boolean | undefined>;
|
|
65
71
|
gamePause: import('canvasengine').WritableSignal<boolean>;
|
|
66
72
|
private predictionEnabled;
|
|
67
73
|
private prediction?;
|
|
@@ -157,6 +163,7 @@ export declare class RpgClientEngine<T = any> {
|
|
|
157
163
|
private setupPointerTracking;
|
|
158
164
|
updatePointerFromInteractionEvent(event: any): void;
|
|
159
165
|
private findViewportInstance;
|
|
166
|
+
private clearCameraFollowViewportPlugins;
|
|
160
167
|
private prepareSyncPayload;
|
|
161
168
|
private shouldPreserveLocalPlayerPosition;
|
|
162
169
|
private shouldKeepLocalPlayerMovement;
|
|
@@ -401,19 +408,22 @@ export declare class RpgClientEngine<T = any> {
|
|
|
401
408
|
* Set the camera to follow a specific sprite
|
|
402
409
|
*
|
|
403
410
|
* This method changes which sprite the camera viewport should follow.
|
|
404
|
-
* The camera
|
|
411
|
+
* The camera can smoothly animate to the target sprite before continuous follow starts.
|
|
405
412
|
*
|
|
406
413
|
* ## Design
|
|
407
414
|
*
|
|
408
415
|
* The camera follow target is stored in a signal that is read by sprite components.
|
|
409
416
|
* Each sprite checks if it should be followed by comparing its ID with the target ID.
|
|
410
|
-
* When smoothMove options are provided, the
|
|
411
|
-
* viewport system.
|
|
417
|
+
* When smoothMove options are provided, the transition is handled by pixi-viewport's
|
|
418
|
+
* animation plugin, then continuous follow is handled by CanvasEngine's viewport system.
|
|
412
419
|
*
|
|
413
420
|
* @param targetId - The ID of the sprite to follow. Set to null to follow the current player
|
|
414
421
|
* @param smoothMove - Animation options. Can be a boolean (default: true) or an object with time and ease
|
|
415
422
|
* @param smoothMove.time - Duration of the animation in milliseconds (optional)
|
|
416
423
|
* @param smoothMove.ease - Easing function name from https://easings.net (optional)
|
|
424
|
+
* @param smoothMove.speed - Continuous follow speed after the transition (optional)
|
|
425
|
+
* @param smoothMove.acceleration - Continuous follow acceleration after the transition (optional)
|
|
426
|
+
* @param smoothMove.radius - Center radius where the target can move without moving the viewport (optional)
|
|
417
427
|
*
|
|
418
428
|
* @example
|
|
419
429
|
* ```ts
|
|
@@ -433,10 +443,8 @@ export declare class RpgClientEngine<T = any> {
|
|
|
433
443
|
* engine.setCameraFollow(null);
|
|
434
444
|
* ```
|
|
435
445
|
*/
|
|
436
|
-
setCameraFollow(targetId: string | null, smoothMove?:
|
|
437
|
-
|
|
438
|
-
ease?: string;
|
|
439
|
-
}): void;
|
|
446
|
+
setCameraFollow(targetId: string | null, smoothMove?: CameraFollowSmoothMove): void;
|
|
447
|
+
private resetCameraFollow;
|
|
440
448
|
addParticle(particle: any): any;
|
|
441
449
|
/**
|
|
442
450
|
* Add a component to render behind sprites
|
|
@@ -689,6 +697,8 @@ export declare class RpgClientEngine<T = any> {
|
|
|
689
697
|
private flushPendingMovePath;
|
|
690
698
|
private applyPredictedMovementInput;
|
|
691
699
|
private getLocalPlayerState;
|
|
700
|
+
private resolveHitboxDimension;
|
|
701
|
+
private resolveObjectHitboxSize;
|
|
692
702
|
private applyAuthoritativeState;
|
|
693
703
|
private initializePredictionController;
|
|
694
704
|
getCurrentPlayer(): import('.').RpgClientPlayer;
|
|
@@ -845,4 +855,3 @@ export declare class RpgClientEngine<T = any> {
|
|
|
845
855
|
*/
|
|
846
856
|
clear(): void;
|
|
847
857
|
}
|
|
848
|
-
export {};
|
package/dist/RpgClientEngine.js
CHANGED
|
@@ -24,6 +24,7 @@ import { ProjectileManager } from "./Game/ProjectileManager.js";
|
|
|
24
24
|
import { ClientVisualRegistry } from "./Game/ClientVisuals.js";
|
|
25
25
|
import { createClientPointerContext } from "./services/pointerContext.js";
|
|
26
26
|
import { RpgClientInteractions } from "./services/interactions.js";
|
|
27
|
+
import { applySyncedHitboxPayload } from "./utils/syncHitbox.js";
|
|
27
28
|
import { EventComponentResolverRegistry } from "./Game/EventComponentResolver.js";
|
|
28
29
|
import { RpgClientBuiltinI18n } from "./i18n.js";
|
|
29
30
|
import { Howl, bootstrapCanvas, signal, trigger } from "canvasengine";
|
|
@@ -106,6 +107,8 @@ var RpgClientEngine = class {
|
|
|
106
107
|
this.spriteComponents = /* @__PURE__ */ new Map();
|
|
107
108
|
this.eventComponentResolvers = new EventComponentResolverRegistry();
|
|
108
109
|
this.cameraFollowTargetId = signal(null);
|
|
110
|
+
this.cameraFollowSmoothMove = false;
|
|
111
|
+
this.cameraFollowRevision = signal(0);
|
|
109
112
|
this.mapShakeTrigger = trigger();
|
|
110
113
|
this.controlsReady = signal(void 0);
|
|
111
114
|
this.gamePause = signal(false);
|
|
@@ -231,7 +234,7 @@ var RpgClientEngine = class {
|
|
|
231
234
|
...currentValues,
|
|
232
235
|
values: new Map([["__default__", controlInstance]])
|
|
233
236
|
};
|
|
234
|
-
this.controlsReady.set(
|
|
237
|
+
this.controlsReady.set(true);
|
|
235
238
|
}
|
|
236
239
|
async start() {
|
|
237
240
|
this.sceneMap = new RpgClientMap();
|
|
@@ -427,6 +430,11 @@ var RpgClientEngine = class {
|
|
|
427
430
|
};
|
|
428
431
|
return find(this.canvasApp?.stage);
|
|
429
432
|
}
|
|
433
|
+
clearCameraFollowViewportPlugins() {
|
|
434
|
+
const viewport = this.findViewportInstance();
|
|
435
|
+
viewport?.plugins?.remove?.("animate");
|
|
436
|
+
viewport?.plugins?.remove?.("follow");
|
|
437
|
+
}
|
|
430
438
|
prepareSyncPayload(data) {
|
|
431
439
|
const payload = { ...data ?? {} };
|
|
432
440
|
delete payload.ack;
|
|
@@ -626,7 +634,7 @@ var RpgClientEngine = class {
|
|
|
626
634
|
this.sceneMap.clearLightSpots();
|
|
627
635
|
this.clearComponentAnimations();
|
|
628
636
|
this.projectiles.setMapId(nextMapId);
|
|
629
|
-
this.
|
|
637
|
+
this.resetCameraFollow(false);
|
|
630
638
|
this.sceneMap.reset();
|
|
631
639
|
this.sceneMap.loadPhysic();
|
|
632
640
|
}
|
|
@@ -679,6 +687,7 @@ var RpgClientEngine = class {
|
|
|
679
687
|
const normalizedAck = ack && typeof ack.frame === "number" ? this.normalizeAckWithSyncState(ack, data) : void 0;
|
|
680
688
|
const payload = this.prepareSyncPayload(data);
|
|
681
689
|
load(this.sceneMap, payload, true);
|
|
690
|
+
applySyncedHitboxPayload(this.sceneMap, payload);
|
|
682
691
|
if (normalizedAck) this.applyServerAck(normalizedAck);
|
|
683
692
|
for (const playerId in payload.players ?? {}) {
|
|
684
693
|
const player = payload.players[playerId];
|
|
@@ -1060,19 +1069,22 @@ var RpgClientEngine = class {
|
|
|
1060
1069
|
* Set the camera to follow a specific sprite
|
|
1061
1070
|
*
|
|
1062
1071
|
* This method changes which sprite the camera viewport should follow.
|
|
1063
|
-
* The camera
|
|
1072
|
+
* The camera can smoothly animate to the target sprite before continuous follow starts.
|
|
1064
1073
|
*
|
|
1065
1074
|
* ## Design
|
|
1066
1075
|
*
|
|
1067
1076
|
* The camera follow target is stored in a signal that is read by sprite components.
|
|
1068
1077
|
* Each sprite checks if it should be followed by comparing its ID with the target ID.
|
|
1069
|
-
* When smoothMove options are provided, the
|
|
1070
|
-
* viewport system.
|
|
1078
|
+
* When smoothMove options are provided, the transition is handled by pixi-viewport's
|
|
1079
|
+
* animation plugin, then continuous follow is handled by CanvasEngine's viewport system.
|
|
1071
1080
|
*
|
|
1072
1081
|
* @param targetId - The ID of the sprite to follow. Set to null to follow the current player
|
|
1073
1082
|
* @param smoothMove - Animation options. Can be a boolean (default: true) or an object with time and ease
|
|
1074
1083
|
* @param smoothMove.time - Duration of the animation in milliseconds (optional)
|
|
1075
1084
|
* @param smoothMove.ease - Easing function name from https://easings.net (optional)
|
|
1085
|
+
* @param smoothMove.speed - Continuous follow speed after the transition (optional)
|
|
1086
|
+
* @param smoothMove.acceleration - Continuous follow acceleration after the transition (optional)
|
|
1087
|
+
* @param smoothMove.radius - Center radius where the target can move without moving the viewport (optional)
|
|
1076
1088
|
*
|
|
1077
1089
|
* @example
|
|
1078
1090
|
* ```ts
|
|
@@ -1093,8 +1105,16 @@ var RpgClientEngine = class {
|
|
|
1093
1105
|
* ```
|
|
1094
1106
|
*/
|
|
1095
1107
|
setCameraFollow(targetId, smoothMove) {
|
|
1108
|
+
this.clearCameraFollowViewportPlugins();
|
|
1109
|
+
this.cameraFollowSmoothMove = smoothMove ?? true;
|
|
1096
1110
|
this.cameraFollowTargetId.set(targetId);
|
|
1097
|
-
|
|
1111
|
+
this.cameraFollowRevision.set(this.cameraFollowRevision() + 1);
|
|
1112
|
+
}
|
|
1113
|
+
resetCameraFollow(smoothMove = false) {
|
|
1114
|
+
this.clearCameraFollowViewportPlugins();
|
|
1115
|
+
this.cameraFollowSmoothMove = smoothMove;
|
|
1116
|
+
this.cameraFollowTargetId.set(null);
|
|
1117
|
+
this.cameraFollowRevision.set(this.cameraFollowRevision() + 1);
|
|
1098
1118
|
}
|
|
1099
1119
|
addParticle(particle) {
|
|
1100
1120
|
this.particleSettings.emitters.push(particle);
|
|
@@ -1585,12 +1605,21 @@ var RpgClientEngine = class {
|
|
|
1585
1605
|
direction: currentPlayer.direction()
|
|
1586
1606
|
};
|
|
1587
1607
|
}
|
|
1608
|
+
resolveHitboxDimension(source, fallback) {
|
|
1609
|
+
const value = typeof source === "string" ? Number(source) : source;
|
|
1610
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
1611
|
+
}
|
|
1612
|
+
resolveObjectHitboxSize(object) {
|
|
1613
|
+
const hitbox = typeof object?.hitbox === "function" ? object.hitbox() : object?.hitbox;
|
|
1614
|
+
return {
|
|
1615
|
+
width: this.resolveHitboxDimension(hitbox?.w ?? hitbox?.width, 0),
|
|
1616
|
+
height: this.resolveHitboxDimension(hitbox?.h ?? hitbox?.height, 0)
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1588
1619
|
applyAuthoritativeState(state) {
|
|
1589
1620
|
const player = this.sceneMap?.getCurrentPlayer();
|
|
1590
1621
|
if (!player) return;
|
|
1591
|
-
const
|
|
1592
|
-
const width = hitbox?.w ?? 0;
|
|
1593
|
-
const height = hitbox?.h ?? 0;
|
|
1622
|
+
const { width, height } = this.resolveObjectHitboxSize(player);
|
|
1594
1623
|
if (!this.sceneMap.updateHitbox(player.id, state.x, state.y, width, height)) this.sceneMap.setBodyPosition(player.id, state.x, state.y, "top-left");
|
|
1595
1624
|
player.x.set(Math.round(state.x));
|
|
1596
1625
|
player.y.set(Math.round(state.y));
|
|
@@ -1777,9 +1806,7 @@ var RpgClientEngine = class {
|
|
|
1777
1806
|
const player = this.getCurrentPlayer();
|
|
1778
1807
|
const myId = this.playerIdSignal();
|
|
1779
1808
|
if (!player || !myId) return;
|
|
1780
|
-
const
|
|
1781
|
-
const width = hitbox?.w ?? 0;
|
|
1782
|
-
const height = hitbox?.h ?? 0;
|
|
1809
|
+
const { width, height } = this.resolveObjectHitboxSize(player);
|
|
1783
1810
|
if (!this.sceneMap.updateHitbox(myId, ack.x, ack.y, width, height)) this.sceneMap.setBodyPosition(myId, ack.x, ack.y, "top-left");
|
|
1784
1811
|
player.x.set(Math.round(ack.x));
|
|
1785
1812
|
player.y.set(Math.round(ack.y));
|
|
@@ -1905,7 +1932,7 @@ var RpgClientEngine = class {
|
|
|
1905
1932
|
}
|
|
1906
1933
|
if (this.prediction) this.prediction = void 0;
|
|
1907
1934
|
this.playerIdSignal.set(null);
|
|
1908
|
-
this.
|
|
1935
|
+
this.resetCameraFollow(false);
|
|
1909
1936
|
this.spriteComponentsBehind.set([]);
|
|
1910
1937
|
this.spriteComponentsInFront.set([]);
|
|
1911
1938
|
this.eventComponentResolvers.clear();
|