@rpgjs/client 5.0.0-beta.26 → 5.0.0-beta.28

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.
Files changed (135) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/Game/AnimationManager.js.map +1 -1
  3. package/dist/Game/ClientVisuals.js.map +1 -1
  4. package/dist/Game/Map.js +2 -2
  5. package/dist/Game/Map.js.map +1 -1
  6. package/dist/Game/Object.js +1 -1
  7. package/dist/Game/Object.js.map +1 -1
  8. package/dist/Game/ProjectileManager.js.map +1 -1
  9. package/dist/Gui/Gui.d.ts +36 -17
  10. package/dist/Gui/Gui.js +40 -22
  11. package/dist/Gui/Gui.js.map +1 -1
  12. package/dist/Gui/NotificationManager.js.map +1 -1
  13. package/dist/RpgClient.d.ts +3 -64
  14. package/dist/RpgClientEngine.js +1 -1
  15. package/dist/RpgClientEngine.js.map +1 -1
  16. package/dist/_virtual/{_@oxc-project_runtime@0.133.0 → _@oxc-project_runtime@0.139.0}/helpers/esm/decorate.js +2 -2
  17. package/dist/_virtual/{_@oxc-project_runtime@0.133.0 → _@oxc-project_runtime@0.139.0}/helpers/esm/decorateMetadata.js +2 -2
  18. package/dist/components/animations/animation.ce.js.map +1 -1
  19. package/dist/components/animations/fx.ce.js.map +1 -1
  20. package/dist/components/animations/hit.ce.js.map +1 -1
  21. package/dist/components/character-hitbox.js +1 -1
  22. package/dist/components/character.ce.js +3 -1
  23. package/dist/components/character.ce.js.map +1 -1
  24. package/dist/components/dynamics/bar.ce.js.map +1 -1
  25. package/dist/components/dynamics/image.ce.js.map +1 -1
  26. package/dist/components/dynamics/parse-value.js +1 -1
  27. package/dist/components/dynamics/parse-value.js.map +1 -1
  28. package/dist/components/dynamics/shape-utils.js +1 -1
  29. package/dist/components/dynamics/shape.ce.js.map +1 -1
  30. package/dist/components/dynamics/text.ce.js.map +1 -1
  31. package/dist/components/gui/box.ce.js.map +1 -1
  32. package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
  33. package/dist/components/gui/gameover.ce.js.map +1 -1
  34. package/dist/components/gui/hud/hud.ce.js.map +1 -1
  35. package/dist/components/gui/index.js +17 -16
  36. package/dist/components/gui/input-field.ce.js.map +1 -1
  37. package/dist/components/gui/input.ce.js.map +1 -1
  38. package/dist/components/gui/menu/equip-menu.ce.js +1 -1
  39. package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
  40. package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
  41. package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
  42. package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
  43. package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
  44. package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
  45. package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
  46. package/dist/components/gui/notification/notification.ce.js.map +1 -1
  47. package/dist/components/gui/save-load.ce.js.map +1 -1
  48. package/dist/components/gui/shop/shop.ce.js +4 -2
  49. package/dist/components/gui/shop/shop.ce.js.map +1 -1
  50. package/dist/components/gui/title-screen.ce.js.map +1 -1
  51. package/dist/components/index.js +4 -3
  52. package/dist/components/interaction-components.ce.js.map +1 -1
  53. package/dist/components/player-components-utils.js +1 -1
  54. package/dist/components/player-components-utils.js.map +1 -1
  55. package/dist/components/player-components.ce.js.map +1 -1
  56. package/dist/components/prebuilt/hp-bar.ce.js.map +1 -1
  57. package/dist/components/prebuilt/index.js +3 -2
  58. package/dist/components/prebuilt/light-halo.ce.js.map +1 -1
  59. package/dist/components/scenes/canvas.ce.js.map +1 -1
  60. package/dist/components/scenes/draw-map.ce.js.map +1 -1
  61. package/dist/components/scenes/event-layer.ce.js.map +1 -1
  62. package/dist/components/scenes/weather-tick-lifecycle.js.map +1 -1
  63. package/dist/core/inject.d.ts +4 -4
  64. package/dist/core/inject.js.map +1 -1
  65. package/dist/core/setup.d.ts +3 -3
  66. package/dist/core/setup.js +1 -1
  67. package/dist/core/setup.js.map +1 -1
  68. package/dist/core/setup.spec.d.ts +1 -0
  69. package/dist/i18n.d.ts +1 -1
  70. package/dist/index.d.ts +1 -1
  71. package/dist/index.js +1 -2
  72. package/dist/module.d.ts +2 -2
  73. package/dist/module.js.map +1 -1
  74. package/dist/node_modules/.pnpm/@signe_di@3.1.0/node_modules/@signe/di/dist/index.js +1 -1
  75. package/dist/node_modules/.pnpm/@signe_reactive@3.1.0/node_modules/@signe/reactive/dist/index.js +1 -1
  76. package/dist/node_modules/.pnpm/@signe_reactive@3.1.0/node_modules/@signe/reactive/dist/index.js.map +1 -1
  77. package/dist/node_modules/.pnpm/@signe_room@3.1.0_react@19.2.7/node_modules/@signe/room/dist/index.js +2 -2
  78. package/dist/node_modules/.pnpm/@signe_room@3.1.0_react@19.2.7/node_modules/@signe/room/dist/index.js.map +1 -1
  79. package/dist/node_modules/.pnpm/@signe_sync@3.1.0_react@19.2.7/node_modules/@signe/sync/dist/client/index.js +1 -1
  80. package/dist/node_modules/.pnpm/@signe_sync@3.1.0_react@19.2.7/node_modules/@signe/sync/dist/index.js +1 -1
  81. package/dist/node_modules/.pnpm/@signe_sync@3.1.0_react@19.2.7/node_modules/@signe/sync/dist/index.js.map +1 -1
  82. package/dist/node_modules/.pnpm/partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/index.js +2 -2
  83. package/dist/node_modules/.pnpm/partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/index.js.map +1 -1
  84. package/dist/node_modules/.pnpm/partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/ws.js +1 -1
  85. package/dist/node_modules/.pnpm/partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/ws.js.map +1 -1
  86. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/index.js +7 -7
  87. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.js.map +1 -1
  88. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.js +1 -1
  89. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js +2 -2
  90. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js.map +1 -1
  91. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js.map +1 -1
  92. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js.map +1 -1
  93. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js +1 -1
  94. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js.map +1 -1
  95. package/dist/presets/faceset.js.map +1 -1
  96. package/dist/presets/rmspritesheet.js.map +1 -1
  97. package/dist/public-api-types.spec.d.ts +1 -0
  98. package/dist/services/AbstractSocket.d.ts +3 -3
  99. package/dist/services/AbstractSocket.js.map +1 -1
  100. package/dist/services/cameraFollow.js +1 -1
  101. package/dist/services/cameraFollow.js.map +1 -1
  102. package/dist/services/interactions.js.map +1 -1
  103. package/dist/services/loadMap.d.ts +3 -7
  104. package/dist/services/loadMap.js +2 -2
  105. package/dist/services/loadMap.js.map +1 -1
  106. package/dist/services/mapStreaming.d.ts +2 -16
  107. package/dist/services/mapStreaming.js.map +1 -1
  108. package/dist/services/mmorpg-connection.js +1 -1
  109. package/dist/services/mmorpg-connection.js.map +1 -1
  110. package/dist/services/mmorpg.d.ts +4 -26
  111. package/dist/services/mmorpg.js.map +1 -1
  112. package/dist/services/pointerContext.js +2 -1
  113. package/dist/services/pointerContext.js.map +1 -1
  114. package/dist/services/save.js.map +1 -1
  115. package/dist/services/standalone.d.ts +2 -98
  116. package/dist/services/standalone.js.map +1 -1
  117. package/dist/utils/getEntityProp.js.map +1 -1
  118. package/dist/utils/syncHitbox.js.map +1 -1
  119. package/package.json +10 -10
  120. package/src/Game/Object.spec.ts +1 -1
  121. package/src/Game/Object.ts +4 -4
  122. package/src/Gui/Gui.spec.ts +26 -1
  123. package/src/Gui/Gui.ts +104 -40
  124. package/src/RpgClient.ts +2 -63
  125. package/src/core/inject.ts +6 -5
  126. package/src/core/setup.spec.ts +35 -0
  127. package/src/core/setup.ts +8 -6
  128. package/src/index.ts +12 -1
  129. package/src/module.ts +2 -2
  130. package/src/public-api-types.spec.ts +37 -0
  131. package/src/services/AbstractSocket.ts +2 -2
  132. package/src/services/loadMap.ts +8 -8
  133. package/src/services/mapStreaming.ts +7 -5
  134. package/src/services/mmorpg.ts +6 -7
  135. package/src/services/standalone.ts +4 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # @rpgjs/client
2
2
 
3
+ ## 5.0.0-beta.28
4
+
5
+ ### Minor Changes
6
+
7
+ - 995277f: Add explicit renderer-neutral GUI registrations with an official Vue helper,
8
+ ship the authoritative and replaceable RPGJS chat module, provide default and
9
+ pixel chat themes over shared semantic CSS primitives, and enforce client/server
10
+ production bundle isolation with executable fixtures. Include a runnable pixel
11
+ chat playground and keep chat socket listeners active across standalone startup
12
+ and player map transfers. Document client/server setup, moderation hooks,
13
+ replacement components, themes, translations, and the public chat state API.
14
+ Emit complete side-specific chat declarations, synchronize the built-in input
15
+ length with client configuration, and reject explicitly unknown chat channels.
16
+
17
+ ### Patch Changes
18
+
19
+ - 995277f: Upgrade the RPGJS workspace and published package compatibility ranges to
20
+ CanvasEngine 2.1, including its compiler, presets, testing, and Tiled
21
+ integrations. Keep PixiJS on the CanvasEngine-supported 8.19 line and verify the
22
+ existing client, server, sample, and playground builds against the new runtime.
23
+ - Updated dependencies [37a4fb0]
24
+ - Updated dependencies [995277f]
25
+ - Updated dependencies [995277f]
26
+ - Updated dependencies [335b768]
27
+ - @rpgjs/server@5.0.0-beta.28
28
+ - @rpgjs/ui-css@5.0.0-beta.24
29
+
30
+ ## 5.0.0-beta.27
31
+
32
+ ### Patch Changes
33
+
34
+ - dc6aed5: Initialize recursively nested RPGJS provider lists in client and server setup so
35
+ sample and consumer configurations register every provider at runtime.
36
+ - e5ad24a: Establish the stable RPGJS-owned boundary for reactive gameplay properties,
37
+ dependency-injection providers, Node room storage, and Cloudflare room hosting.
38
+ Remove accidental Signe re-exports from the client and server roots, keep
39
+ direct Signe imports as an explicitly advanced plugin path, and protect every
40
+ published TypeScript entry with declaration reachability snapshots in CI.
41
+ Keep provider creation strategies mutually exclusive, support asynchronous
42
+ provider factories, and preserve strict member checking on the server engine.
43
+ Enforce these public contracts in CI, test the RPGJS-owned Node storage
44
+ lifecycle, and document complete stable migration examples.
45
+ - Updated dependencies [dc6aed5]
46
+ - Updated dependencies [e5ad24a]
47
+ - @rpgjs/server@5.0.0-beta.27
48
+ - @rpgjs/common@5.0.0-beta.26
49
+
3
50
  ## 5.0.0-beta.26
4
51
 
5
52
  ### Minor Changes
@@ -1 +1 @@
1
- {"version":3,"file":"AnimationManager.js","names":[],"sources":["../../src/Game/AnimationManager.ts"],"sourcesContent":["import { generateUID, RpgCommonPlayer } from \"@rpgjs/common\";\nimport { signal } from \"canvasengine\";\n\nexport class AnimationManager {\n current = signal<any[]>([]);\n\n clear(): void {\n this.current.set([]);\n }\n\n displayEffect(params: any, player: RpgCommonPlayer | { x: number, y: number }): Promise<void> {\n const id = generateUID();\n const effectParams = params ?? {};\n return new Promise<void>((resolve) => {\n let finished = false;\n const finish = (data?: any) => {\n if (finished) return;\n finished = true;\n const index = this.current().findIndex((value) => value.id === id);\n if (index !== -1) {\n this.current().splice(index, 1);\n }\n effectParams.onFinish?.(data);\n resolve();\n };\n\n this.current().push({\n ...effectParams,\n id,\n x: player.x,\n y: player.y,\n object: player,\n onFinish: finish,\n });\n });\n }\n}\n"],"mappings":";;;AAGA,IAAa,mBAAb,MAA8B;;iBAClB,OAAc,CAAC,CAAC;;CAE1B,QAAc;EACZ,KAAK,QAAQ,IAAI,CAAC,CAAC;CACrB;CAEA,cAAc,QAAa,QAAmE;EAC5F,MAAM,KAAK,YAAY;EACvB,MAAM,eAAe,UAAU,CAAC;EAChC,OAAO,IAAI,SAAe,YAAY;GACpC,IAAI,WAAW;GACf,MAAM,UAAU,SAAe;IAC7B,IAAI,UAAU;IACd,WAAW;IACX,MAAM,QAAQ,KAAK,QAAQ,EAAE,WAAW,UAAU,MAAM,OAAO,EAAE;IACjE,IAAI,UAAU,IACZ,KAAK,QAAQ,EAAE,OAAO,OAAO,CAAC;IAEhC,aAAa,WAAW,IAAI;IAC5B,QAAQ;GACV;GAEA,KAAK,QAAQ,EAAE,KAAK;IAClB,GAAG;IACH;IACA,GAAG,OAAO;IACV,GAAG,OAAO;IACV,QAAQ;IACR,UAAU;GACZ,CAAC;EACH,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"AnimationManager.js","names":[],"sources":["../../src/Game/AnimationManager.ts"],"sourcesContent":["import { generateUID, RpgCommonPlayer } from \"@rpgjs/common\";\nimport { signal } from \"canvasengine\";\n\nexport class AnimationManager {\n current = signal<any[]>([]);\n\n clear(): void {\n this.current.set([]);\n }\n\n displayEffect(params: any, player: RpgCommonPlayer | { x: number, y: number }): Promise<void> {\n const id = generateUID();\n const effectParams = params ?? {};\n return new Promise<void>((resolve) => {\n let finished = false;\n const finish = (data?: any) => {\n if (finished) return;\n finished = true;\n const index = this.current().findIndex((value) => value.id === id);\n if (index !== -1) {\n this.current().splice(index, 1);\n }\n effectParams.onFinish?.(data);\n resolve();\n };\n\n this.current().push({\n ...effectParams,\n id,\n x: player.x,\n y: player.y,\n object: player,\n onFinish: finish,\n });\n });\n }\n}\n"],"mappings":";;;AAGA,IAAa,mBAAb,MAA8B;;iBAClB,OAAc,CAAC,CAAC;;CAE1B,QAAc;EACZ,KAAK,QAAQ,IAAI,CAAC,CAAC;CACrB;CAEA,cAAc,QAAa,QAAmE;EAC5F,MAAM,KAAK,YAAY;EACvB,MAAM,eAAe,UAAU,CAAC;EAChC,OAAO,IAAI,SAAe,YAAY;GACpC,IAAI,WAAW;GACf,MAAM,UAAU,SAAe;IAC7B,IAAI,UAAU;IACd,WAAW;IACX,MAAM,QAAQ,KAAK,QAAQ,CAAC,CAAC,WAAW,UAAU,MAAM,OAAO,EAAE;IACjE,IAAI,UAAU,IACZ,KAAK,QAAQ,CAAC,CAAC,OAAO,OAAO,CAAC;IAEhC,aAAa,WAAW,IAAI;IAC5B,QAAQ;GACV;GAEA,KAAK,QAAQ,CAAC,CAAC,KAAK;IAClB,GAAG;IACH;IACA,GAAG,OAAO;IACV,GAAG,OAAO;IACV,QAAQ;IACR,UAAU;GACZ,CAAC;EACH,CAAC;CACH;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"ClientVisuals.js","names":[],"sources":["../../src/Game/ClientVisuals.ts"],"sourcesContent":["import type { RpgClientEngine } from \"../RpgClientEngine\";\nimport type { RpgClientMap } from \"./Map\";\n\nexport type ClientVisualPosition = {\n x: number;\n y: number;\n z?: number;\n};\n\nexport type ClientVisualPayload = Record<string, any> & {\n source?: string;\n sourceId?: string;\n target?: string;\n targetId?: string;\n object?: string;\n objectId?: string;\n position?: ClientVisualPosition;\n};\n\nexport type ClientVisualPacket = {\n name: string;\n data?: ClientVisualPayload;\n};\n\nexport type ClientVisualObjectTarget = string | Record<string, any> | undefined | null;\nexport type ClientVisualComponentTarget =\n | ClientVisualObjectTarget\n | ClientVisualPosition;\n\nexport type ClientVisualContext = {\n name: string;\n data: ClientVisualPayload;\n engine: RpgClientEngine;\n scene: RpgClientMap;\n source?: any;\n target?: any;\n object?: any;\n position?: ClientVisualPosition;\n};\n\nexport type ClientVisualHelpers = {\n getObject(id?: string | null): any;\n flash(target?: ClientVisualObjectTarget, options?: Record<string, any>): void;\n showHit(target: ClientVisualObjectTarget, text: string): void;\n component(\n id: string,\n target?: ClientVisualComponentTarget,\n params?: Record<string, any>\n ): void;\n sound(id: string, options?: { volume?: number; loop?: boolean }): Promise<void>;\n animation(\n target: ClientVisualObjectTarget,\n animationName: string,\n options?: { graphic?: string | string[]; repeat?: number }\n ): void;\n shake(options?: {\n intensity?: number;\n duration?: number;\n frequency?: number;\n direction?: string;\n }): void;\n};\n\nexport type ClientVisualHandler = (\n context: ClientVisualContext,\n helpers: ClientVisualHelpers\n) => void | Promise<void>;\n\nexport type ClientVisualMap = Record<string, ClientVisualHandler>;\n\nconst isPosition = (value: any): value is ClientVisualPosition =>\n value &&\n typeof value === \"object\" &&\n typeof value.x === \"number\" &&\n typeof value.y === \"number\";\n\nconst resolvePosition = (data: ClientVisualPayload): ClientVisualPosition | undefined => {\n if (isPosition(data.position)) return data.position;\n if (typeof data.x === \"number\" && typeof data.y === \"number\") {\n return { x: data.x, y: data.y, z: data.z };\n }\n return undefined;\n};\n\nconst resolveObject = (engine: RpgClientEngine, target: ClientVisualObjectTarget) => {\n if (!target) return undefined;\n if (typeof target === \"string\") return engine.getObjectById(target);\n return target;\n};\n\nconst resolvePayloadObject = (\n engine: RpgClientEngine,\n data: ClientVisualPayload,\n keys: string[]\n) => {\n const id = keys\n .map((key) => data[key])\n .find((value) => typeof value === \"string\");\n return resolveObject(engine, id);\n};\n\nconst createClientVisualHelpers = (\n engine: RpgClientEngine\n): ClientVisualHelpers => ({\n getObject(id) {\n if (!id) return undefined;\n return engine.getObjectById(id);\n },\n flash(target, options = {}) {\n const object = resolveObject(engine, target);\n object?.flash?.(options);\n },\n showHit(target, text) {\n const object = resolveObject(engine, target);\n object?.showHit?.(text);\n },\n component(id, target, params = {}) {\n const object = isPosition(target) ? undefined : resolveObject(engine, target);\n const position = isPosition(target) ? target : undefined;\n const anchor = object ?? position;\n if (!anchor) return;\n engine.getComponentAnimation(id).displayEffect(params, anchor);\n },\n sound(id, options) {\n return engine.playSound(id, options);\n },\n animation(target, animationName, options = {}) {\n const object = resolveObject(engine, target);\n if (!object?.setAnimation) return;\n if (options.graphic !== undefined) {\n object.setAnimation(animationName, options.graphic, options.repeat ?? 1);\n return;\n }\n object.setAnimation(animationName, options.repeat ?? 1);\n },\n shake(options = {}) {\n engine.mapShakeTrigger.start(options);\n },\n});\n\nexport class ClientVisualRegistry {\n private readonly handlers = new Map<string, ClientVisualHandler>();\n\n register(name: string, handler: ClientVisualHandler) {\n this.handlers.set(name, handler);\n return handler;\n }\n\n registerMany(visuals: ClientVisualMap) {\n Object.entries(visuals).forEach(([name, handler]) => {\n this.register(name, handler);\n });\n }\n\n get(name: string) {\n return this.handlers.get(name);\n }\n\n async play(packet: ClientVisualPacket, engine: RpgClientEngine) {\n const handler = this.handlers.get(packet.name);\n if (!handler) {\n console.warn(`Client visual \"${packet.name}\" is not registered`);\n return;\n }\n\n const data = packet.data ?? {};\n const context: ClientVisualContext = {\n name: packet.name,\n data,\n engine,\n scene: engine.scene,\n source: resolvePayloadObject(engine, data, [\"sourceId\", \"source\"]),\n target: resolvePayloadObject(engine, data, [\"targetId\", \"target\"]),\n object: resolvePayloadObject(engine, data, [\"objectId\", \"object\"]),\n position: resolvePosition(data),\n };\n\n try {\n await handler(context, createClientVisualHelpers(engine));\n } catch (error) {\n console.error(`Client visual \"${packet.name}\" failed`, error);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ClientVisuals.js","names":[],"sources":["../../src/Game/ClientVisuals.ts"],"sourcesContent":["import type { RpgClientEngine } from \"../RpgClientEngine\";\nimport type { RpgClientMap } from \"./Map\";\n\nexport type ClientVisualPosition = {\n x: number;\n y: number;\n z?: number;\n};\n\nexport type ClientVisualPayload = Record<string, any> & {\n source?: string;\n sourceId?: string;\n target?: string;\n targetId?: string;\n object?: string;\n objectId?: string;\n position?: ClientVisualPosition;\n};\n\nexport type ClientVisualPacket = {\n name: string;\n data?: ClientVisualPayload;\n};\n\nexport type ClientVisualObjectTarget = string | Record<string, any> | undefined | null;\nexport type ClientVisualComponentTarget =\n | ClientVisualObjectTarget\n | ClientVisualPosition;\n\nexport type ClientVisualContext = {\n name: string;\n data: ClientVisualPayload;\n engine: RpgClientEngine;\n scene: RpgClientMap;\n source?: any;\n target?: any;\n object?: any;\n position?: ClientVisualPosition;\n};\n\nexport type ClientVisualHelpers = {\n getObject(id?: string | null): any;\n flash(target?: ClientVisualObjectTarget, options?: Record<string, any>): void;\n showHit(target: ClientVisualObjectTarget, text: string): void;\n component(\n id: string,\n target?: ClientVisualComponentTarget,\n params?: Record<string, any>\n ): void;\n sound(id: string, options?: { volume?: number; loop?: boolean }): Promise<void>;\n animation(\n target: ClientVisualObjectTarget,\n animationName: string,\n options?: { graphic?: string | string[]; repeat?: number }\n ): void;\n shake(options?: {\n intensity?: number;\n duration?: number;\n frequency?: number;\n direction?: string;\n }): void;\n};\n\nexport type ClientVisualHandler = (\n context: ClientVisualContext,\n helpers: ClientVisualHelpers\n) => void | Promise<void>;\n\nexport type ClientVisualMap = Record<string, ClientVisualHandler>;\n\nconst isPosition = (value: any): value is ClientVisualPosition =>\n value &&\n typeof value === \"object\" &&\n typeof value.x === \"number\" &&\n typeof value.y === \"number\";\n\nconst resolvePosition = (data: ClientVisualPayload): ClientVisualPosition | undefined => {\n if (isPosition(data.position)) return data.position;\n if (typeof data.x === \"number\" && typeof data.y === \"number\") {\n return { x: data.x, y: data.y, z: data.z };\n }\n return undefined;\n};\n\nconst resolveObject = (engine: RpgClientEngine, target: ClientVisualObjectTarget) => {\n if (!target) return undefined;\n if (typeof target === \"string\") return engine.getObjectById(target);\n return target;\n};\n\nconst resolvePayloadObject = (\n engine: RpgClientEngine,\n data: ClientVisualPayload,\n keys: string[]\n) => {\n const id = keys\n .map((key) => data[key])\n .find((value) => typeof value === \"string\");\n return resolveObject(engine, id);\n};\n\nconst createClientVisualHelpers = (\n engine: RpgClientEngine\n): ClientVisualHelpers => ({\n getObject(id) {\n if (!id) return undefined;\n return engine.getObjectById(id);\n },\n flash(target, options = {}) {\n const object = resolveObject(engine, target);\n object?.flash?.(options);\n },\n showHit(target, text) {\n const object = resolveObject(engine, target);\n object?.showHit?.(text);\n },\n component(id, target, params = {}) {\n const object = isPosition(target) ? undefined : resolveObject(engine, target);\n const position = isPosition(target) ? target : undefined;\n const anchor = object ?? position;\n if (!anchor) return;\n engine.getComponentAnimation(id).displayEffect(params, anchor);\n },\n sound(id, options) {\n return engine.playSound(id, options);\n },\n animation(target, animationName, options = {}) {\n const object = resolveObject(engine, target);\n if (!object?.setAnimation) return;\n if (options.graphic !== undefined) {\n object.setAnimation(animationName, options.graphic, options.repeat ?? 1);\n return;\n }\n object.setAnimation(animationName, options.repeat ?? 1);\n },\n shake(options = {}) {\n engine.mapShakeTrigger.start(options);\n },\n});\n\nexport class ClientVisualRegistry {\n private readonly handlers = new Map<string, ClientVisualHandler>();\n\n register(name: string, handler: ClientVisualHandler) {\n this.handlers.set(name, handler);\n return handler;\n }\n\n registerMany(visuals: ClientVisualMap) {\n Object.entries(visuals).forEach(([name, handler]) => {\n this.register(name, handler);\n });\n }\n\n get(name: string) {\n return this.handlers.get(name);\n }\n\n async play(packet: ClientVisualPacket, engine: RpgClientEngine) {\n const handler = this.handlers.get(packet.name);\n if (!handler) {\n console.warn(`Client visual \"${packet.name}\" is not registered`);\n return;\n }\n\n const data = packet.data ?? {};\n const context: ClientVisualContext = {\n name: packet.name,\n data,\n engine,\n scene: engine.scene,\n source: resolvePayloadObject(engine, data, [\"sourceId\", \"source\"]),\n target: resolvePayloadObject(engine, data, [\"targetId\", \"target\"]),\n object: resolvePayloadObject(engine, data, [\"objectId\", \"object\"]),\n position: resolvePosition(data),\n };\n\n try {\n await handler(context, createClientVisualHelpers(engine));\n } catch (error) {\n console.error(`Client visual \"${packet.name}\" failed`, error);\n }\n }\n}\n"],"mappings":"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"}
package/dist/Game/Map.js CHANGED
@@ -2,8 +2,8 @@ import { inject } from "../core/inject.js";
2
2
  import { sync, users } from "../node_modules/.pnpm/@signe_sync@3.1.0_react@19.2.7/node_modules/@signe/sync/dist/index.js";
3
3
  import { RpgClientPlayer } from "./Player.js";
4
4
  import { RpgClientEvent } from "./Event.js";
5
- import { __decorateMetadata } from "../_virtual/_@oxc-project_runtime@0.133.0/helpers/esm/decorateMetadata.js";
6
- import { __decorate } from "../_virtual/_@oxc-project_runtime@0.133.0/helpers/esm/decorate.js";
5
+ import __decorateMetadata from "../_virtual/_@oxc-project_runtime@0.139.0/helpers/esm/decorateMetadata.js";
6
+ import __decorate from "../_virtual/_@oxc-project_runtime@0.139.0/helpers/esm/decorate.js";
7
7
  import { RpgClientEngine } from "../RpgClientEngine.js";
8
8
  import { computed, signal } from "canvasengine";
9
9
  import { RpgCommonMap, cloneLightingState, normalizeLightingState } from "@rpgjs/common";
@@ -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 private localWeatherValue: WeatherState | null = null;\n lightingState = signal<LightingState | null>(null);\n localLightSpots = signal<Record<string, LightSpot>>({});\n weather = computed<WeatherState | null>(() => {\n const local = this.localWeatherOverride() \n const state = this.weatherState()\n return local ?? state\n });\n lighting = computed<LightingState | null>(() => {\n const state = cloneLightingState(this.lightingState());\n const localSpots = Object.entries(this.localLightSpots()).map(([id, spot]) => ({\n ...spot,\n id: spot.id ?? id,\n }));\n\n if (!state && localSpots.length === 0) {\n return null;\n }\n\n const next: LightingState = { ...(state ?? {}) };\n if (!state?.ambient) {\n delete next.ambient;\n }\n next.spots = [\n ...(state?.spots ?? []),\n ...localSpots,\n ];\n return next;\n });\n private manualClientPhysicsTick = false;\n private readonly isTestEnvironment: boolean;\n\n constructor() {\n super();\n // Détecter l'environnement de test\n const testGlobal = globalThis as TestGlobalScope;\n const isTest = testGlobal.process?.env?.TEST === 'true'\n || testGlobal.__RPGJS_TEST__ === true;\n this.isTestEnvironment = isTest;\n if (isTest) {\n this.autoTickEnabled = false;\n }\n }\n\n configureClientPrediction(enabled: boolean): void {\n this.manualClientPhysicsTick = enabled;\n this.autoTickEnabled = enabled ? false : !this.isTestEnvironment;\n }\n\n getCurrentPlayer() {\n return this.currentPlayer()\n }\n\n reset(force = false) {\n const currentPlayerId = this.engine.playerIdSignal();\n const currentPlayer = !force && currentPlayerId\n ? this.players()[currentPlayerId]\n : undefined;\n const players = this.players();\n const events = this.events();\n\n Object.entries(players).forEach(([id, player]) => {\n if (!player || (!force && id === currentPlayerId)) return;\n (player as any).resetAnimationState?.();\n });\n Object.values(events).forEach((event) => {\n (event as any)?.resetAnimationState?.();\n });\n\n this.players.set(\n currentPlayerId && currentPlayer ? { [currentPlayerId]: currentPlayer } : {}\n );\n this.events.set({})\n this.weatherState.set(null);\n this.localWeatherValue = null;\n this.localWeatherOverride.set(null);\n this.lightingState.set(null);\n this.localLightSpots.set({});\n this.clearPhysic()\n }\n\n getWeather(): WeatherState | null {\n return this.weather();\n }\n\n setLocalWeather(next: WeatherState | null): void {\n if (this.localWeatherValue === next) {\n return;\n }\n this.localWeatherValue = next;\n this.localWeatherOverride.set(next);\n }\n\n clearLocalWeather(): void {\n this.localWeatherValue = null;\n this.localWeatherOverride.set(null);\n }\n\n getLighting(): LightingState | null {\n return this.lighting();\n }\n\n addLightSpot(id: string, spot: LightSpot): void {\n const normalized = normalizeLightingState({ spots: [{ ...spot, id }] });\n const nextSpot = normalized?.spots?.[0];\n if (!nextSpot) {\n return;\n }\n this.localLightSpots.update((spots) => {\n if (lightSpotsEqual(spots[id], nextSpot)) {\n return spots;\n }\n return {\n ...spots,\n [id]: nextSpot,\n };\n });\n }\n\n patchLightSpot(id: string, patch: Partial<LightSpot>): void {\n this.localLightSpots.update((spots) => {\n const current = spots[id];\n if (!current) {\n return spots;\n }\n return {\n ...spots,\n [id]: {\n ...current,\n ...patch,\n id,\n x: patch.x ?? current.x,\n y: patch.y ?? current.y,\n },\n };\n });\n }\n\n removeLightSpot(id: string): void {\n this.localLightSpots.update((spots) => {\n if (!(id in spots)) {\n return spots;\n }\n const next = { ...spots };\n delete next[id];\n return next;\n });\n }\n\n clearLightSpots(): void {\n this.localLightSpots.set({});\n }\n\n stepClientPhysics(deltaMs: number): number {\n if (!this.manualClientPhysicsTick) {\n return 0;\n }\n return this.nextTick(deltaMs);\n }\n\n stepPredictionTick(): void {\n this.forceSingleTick();\n }\n\n protected emitPhysicsInit(context: MapPhysicsInitContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsInit\", this, context);\n }\n\n protected emitPhysicsEntityAdd(context: MapPhysicsEntityContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsEntityAdd\", this, context);\n }\n\n protected emitPhysicsEntityRemove(context: MapPhysicsEntityContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsEntityRemove\", this, context);\n }\n\n protected emitPhysicsReset(): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsReset\", this);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Map.js","names":[],"sources":["../../src/Game/Map.ts"],"sourcesContent":["import {\n RpgCommonMap,\n cloneLightingState,\n normalizeLightingState,\n type LightSpot,\n type LightingState,\n type WeatherState,\n type MapPhysicsInitContext,\n type MapPhysicsEntityContext,\n} from \"@rpgjs/common\";\nimport { sync, users } from \"@signe/sync\";\nimport { RpgClientPlayer } from \"./Player\";\nimport { Signal, signal, computed, effect } from \"canvasengine\";\nimport { RpgClientEvent } from \"./Event\";\nimport { RpgClientEngine } from \"../RpgClientEngine\";\nimport { inject } from \"../core/inject\";\n\ntype TestGlobalScope = typeof globalThis & {\n process?: {\n env?: {\n TEST?: string;\n };\n };\n __RPGJS_TEST__?: boolean;\n};\n\nconst lightingColorsEqual = (\n left: LightSpot[\"color\"],\n right: LightSpot[\"color\"],\n): boolean => {\n if (Array.isArray(left) || Array.isArray(right)) {\n return Array.isArray(left)\n && Array.isArray(right)\n && left.length === right.length\n && left.every((value, index) => value === right[index]);\n }\n return left === right;\n};\n\nconst lightSpotsEqual = (left: LightSpot | undefined, right: LightSpot): boolean => {\n if (!left) return false;\n return left.id === right.id\n && left.x === right.x\n && left.y === right.y\n && left.radius === right.radius\n && left.intensity === right.intensity\n && lightingColorsEqual(left.color, right.color)\n && left.flicker === right.flicker\n && left.flickerSpeed === right.flickerSpeed\n && left.pulse === right.pulse\n && left.pulseSpeed === right.pulseSpeed\n && left.phase === right.phase;\n};\n\nexport class RpgClientMap extends RpgCommonMap<any> {\n engine: RpgClientEngine = inject(RpgClientEngine)\n @users(RpgClientPlayer) players = signal<Record<string, RpgClientPlayer>>({});\n @sync(RpgClientEvent) events = signal<Record<string, RpgClientEvent>>({});\n currentPlayer = computed(() => this.players()[this.engine.playerIdSignal()!])\n weatherState = signal<WeatherState | null>(null);\n localWeatherOverride = signal<WeatherState | null>(null);\n private localWeatherValue: WeatherState | null = null;\n lightingState = signal<LightingState | null>(null);\n localLightSpots = signal<Record<string, LightSpot>>({});\n weather = computed<WeatherState | null>(() => {\n const local = this.localWeatherOverride() \n const state = this.weatherState()\n return local ?? state\n });\n lighting = computed<LightingState | null>(() => {\n const state = cloneLightingState(this.lightingState());\n const localSpots = Object.entries(this.localLightSpots()).map(([id, spot]) => ({\n ...spot,\n id: spot.id ?? id,\n }));\n\n if (!state && localSpots.length === 0) {\n return null;\n }\n\n const next: LightingState = { ...(state ?? {}) };\n if (!state?.ambient) {\n delete next.ambient;\n }\n next.spots = [\n ...(state?.spots ?? []),\n ...localSpots,\n ];\n return next;\n });\n private manualClientPhysicsTick = false;\n private readonly isTestEnvironment: boolean;\n\n constructor() {\n super();\n // Détecter l'environnement de test\n const testGlobal = globalThis as TestGlobalScope;\n const isTest = testGlobal.process?.env?.TEST === 'true'\n || testGlobal.__RPGJS_TEST__ === true;\n this.isTestEnvironment = isTest;\n if (isTest) {\n this.autoTickEnabled = false;\n }\n }\n\n configureClientPrediction(enabled: boolean): void {\n this.manualClientPhysicsTick = enabled;\n this.autoTickEnabled = enabled ? false : !this.isTestEnvironment;\n }\n\n getCurrentPlayer() {\n return this.currentPlayer()\n }\n\n reset(force = false) {\n const currentPlayerId = this.engine.playerIdSignal();\n const currentPlayer = !force && currentPlayerId\n ? this.players()[currentPlayerId]\n : undefined;\n const players = this.players();\n const events = this.events();\n\n Object.entries(players).forEach(([id, player]) => {\n if (!player || (!force && id === currentPlayerId)) return;\n (player as any).resetAnimationState?.();\n });\n Object.values(events).forEach((event) => {\n (event as any)?.resetAnimationState?.();\n });\n\n this.players.set(\n currentPlayerId && currentPlayer ? { [currentPlayerId]: currentPlayer } : {}\n );\n this.events.set({})\n this.weatherState.set(null);\n this.localWeatherValue = null;\n this.localWeatherOverride.set(null);\n this.lightingState.set(null);\n this.localLightSpots.set({});\n this.clearPhysic()\n }\n\n getWeather(): WeatherState | null {\n return this.weather();\n }\n\n setLocalWeather(next: WeatherState | null): void {\n if (this.localWeatherValue === next) {\n return;\n }\n this.localWeatherValue = next;\n this.localWeatherOverride.set(next);\n }\n\n clearLocalWeather(): void {\n this.localWeatherValue = null;\n this.localWeatherOverride.set(null);\n }\n\n getLighting(): LightingState | null {\n return this.lighting();\n }\n\n addLightSpot(id: string, spot: LightSpot): void {\n const normalized = normalizeLightingState({ spots: [{ ...spot, id }] });\n const nextSpot = normalized?.spots?.[0];\n if (!nextSpot) {\n return;\n }\n this.localLightSpots.update((spots) => {\n if (lightSpotsEqual(spots[id], nextSpot)) {\n return spots;\n }\n return {\n ...spots,\n [id]: nextSpot,\n };\n });\n }\n\n patchLightSpot(id: string, patch: Partial<LightSpot>): void {\n this.localLightSpots.update((spots) => {\n const current = spots[id];\n if (!current) {\n return spots;\n }\n return {\n ...spots,\n [id]: {\n ...current,\n ...patch,\n id,\n x: patch.x ?? current.x,\n y: patch.y ?? current.y,\n },\n };\n });\n }\n\n removeLightSpot(id: string): void {\n this.localLightSpots.update((spots) => {\n if (!(id in spots)) {\n return spots;\n }\n const next = { ...spots };\n delete next[id];\n return next;\n });\n }\n\n clearLightSpots(): void {\n this.localLightSpots.set({});\n }\n\n stepClientPhysics(deltaMs: number): number {\n if (!this.manualClientPhysicsTick) {\n return 0;\n }\n return this.nextTick(deltaMs);\n }\n\n stepPredictionTick(): void {\n this.forceSingleTick();\n }\n\n protected emitPhysicsInit(context: MapPhysicsInitContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsInit\", this, context);\n }\n\n protected emitPhysicsEntityAdd(context: MapPhysicsEntityContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsEntityAdd\", this, context);\n }\n\n protected emitPhysicsEntityRemove(context: MapPhysicsEntityContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsEntityRemove\", this, context);\n }\n\n protected emitPhysicsReset(): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsReset\", this);\n }\n}\n"],"mappings":"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"}
@@ -321,6 +321,6 @@ var RpgClientObject = class extends RpgCommonPlayer {
321
321
  }
322
322
  };
323
323
  //#endregion
324
- export { RpgClientObject };
324
+ export { RpgClientObject, appendFramePayload, getFrameTimestamp, mergeFreshFramePayload, multiplyGraphicDisplayScale, withGraphicDisplayScale };
325
325
 
326
326
  //# sourceMappingURL=Object.js.map
@@ -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\nconst toFiniteScale = (value: unknown, fallback: number): number => {\n if (typeof value === \"number\" && Number.isFinite(value)) return value;\n if (typeof value === \"string\" && value.trim()) {\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : fallback;\n }\n return fallback;\n};\n\nconst normalizeGraphicScale = (value: unknown, fallback: [number, number] = [1, 1]): [number, number] => {\n if (typeof value === \"number\" || typeof value === \"string\") {\n const scale = toFiniteScale(value, fallback[0]);\n return [scale, scale];\n }\n if (Array.isArray(value)) {\n const x = toFiniteScale(value[0], fallback[0]);\n const y = toFiniteScale(value[1] ?? value[0], x);\n return [x, y];\n }\n if (value && typeof value === \"object\") {\n const scale = value as { x?: unknown; y?: unknown };\n const x = toFiniteScale(scale.x, fallback[0]);\n const y = toFiniteScale(scale.y ?? scale.x, x);\n return [x, y];\n }\n return fallback;\n};\n\nexport const multiplyGraphicDisplayScale = (\n baseScale: unknown,\n instanceScale: unknown,\n): [number, number] => {\n const base = normalizeGraphicScale(baseScale);\n const instance = normalizeGraphicScale(instanceScale);\n return [base[0] * instance[0], base[1] * instance[1]];\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: multiplyGraphicDisplayScale(spritesheet.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 const getFrameTimestamp = (frame: Frame): number => {\n const timestamp = Number(frame.ts);\n return Number.isFinite(timestamp) ? timestamp : 0;\n};\n\nexport const mergeFreshFramePayload = (\n current: unknown,\n items: unknown,\n lastAppliedFrameTs = 0,\n): Frame[] => {\n return appendFramePayload(current, items)\n .filter((frame) => {\n const timestamp = getFrameTimestamp(frame);\n return timestamp === 0 || timestamp > lastAppliedFrameTs;\n })\n .sort((a, b) => getFrameTimestamp(a) - getFrameTimestamp(b));\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 lastAppliedFrameTs = 0;\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 = mergeFreshFramePayload(\n this.frames,\n items,\n this.lastAppliedFrameTs,\n );\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 const frameTs = this.getFrameTimestamp(frame);\n if (frameTs > 0 && frameTs <= this.lastAppliedFrameTs) return;\n engine.scene.setBodyPosition(\n this.id,\n frame.x,\n frame.y,\n \"top-left\"\n );\n if (frameTs > this.lastAppliedFrameTs) {\n this.lastAppliedFrameTs = frameTs;\n }\n }\n });\n }\n\n private getFrameTimestamp(frame: Frame): number {\n return getFrameTimestamp(frame);\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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"}
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, type Observable } 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\nconst toFiniteScale = (value: unknown, fallback: number): number => {\n if (typeof value === \"number\" && Number.isFinite(value)) return value;\n if (typeof value === \"string\" && value.trim()) {\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : fallback;\n }\n return fallback;\n};\n\nconst normalizeGraphicScale = (value: unknown, fallback: [number, number] = [1, 1]): [number, number] => {\n if (typeof value === \"number\" || typeof value === \"string\") {\n const scale = toFiniteScale(value, fallback[0]);\n return [scale, scale];\n }\n if (Array.isArray(value)) {\n const x = toFiniteScale(value[0], fallback[0]);\n const y = toFiniteScale(value[1] ?? value[0], x);\n return [x, y];\n }\n if (value && typeof value === \"object\") {\n const scale = value as { x?: unknown; y?: unknown };\n const x = toFiniteScale(scale.x, fallback[0]);\n const y = toFiniteScale(scale.y ?? scale.x, x);\n return [x, y];\n }\n return fallback;\n};\n\nexport const multiplyGraphicDisplayScale = (\n baseScale: unknown,\n instanceScale: unknown,\n): [number, number] => {\n const base = normalizeGraphicScale(baseScale);\n const instance = normalizeGraphicScale(instanceScale);\n return [base[0] * instance[0], base[1] * instance[1]];\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: multiplyGraphicDisplayScale(spritesheet.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 const getFrameTimestamp = (frame: Frame): number => {\n const timestamp = Number(frame.ts);\n return Number.isFinite(timestamp) ? timestamp : 0;\n};\n\nexport const mergeFreshFramePayload = (\n current: unknown,\n items: unknown,\n lastAppliedFrameTs = 0,\n): Frame[] => {\n return appendFramePayload(current, items)\n .filter((frame) => {\n const timestamp = getFrameTimestamp(frame);\n return timestamp === 0 || timestamp > lastAppliedFrameTs;\n })\n .sort((a, b) => getFrameTimestamp(a) - getFrameTimestamp(b));\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 lastAppliedFrameTs = 0;\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 as any).observable.subscribe(({ items }) => {\n if (!this.id) return;\n //if (this.id == this.engine.playerIdSignal()!) return;\n this.frames = mergeFreshFramePayload(\n this.frames,\n items,\n this.lastAppliedFrameTs,\n );\n });\n\n const graphics$: Observable<unknown> = (this.graphics as any).observable.pipe(map(({ items }) => items));\n const graphicScale$: Observable<unknown> = (this._graphicScale as any).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 const frameTs = this.getFrameTimestamp(frame);\n if (frameTs > 0 && frameTs <= this.lastAppliedFrameTs) return;\n engine.scene.setBodyPosition(\n this.id,\n frame.x,\n frame.y,\n \"top-left\"\n );\n if (frameTs > this.lastAppliedFrameTs) {\n this.lastAppliedFrameTs = frameTs;\n }\n }\n });\n }\n\n private getFrameTimestamp(frame: Frame): number {\n return getFrameTimestamp(frame);\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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectileManager.js","names":[],"sources":["../../src/Game/ProjectileManager.ts"],"sourcesContent":["import { computed, signal } from \"canvasengine\";\nimport { Hooks } from \"@rpgjs/common\";\nimport { normalizeRoomMapId } from \"../utils/mapId\";\n\nexport interface ClientProjectileSpawn {\n id: string;\n type: string;\n ownerId?: string;\n origin: { x: number; y: number };\n direction: { x: number; y: number };\n speed: number;\n range: number;\n ttl: number;\n spawnTick: number;\n delay?: number;\n index?: number;\n count?: number;\n params?: Record<string, unknown>;\n collisionMask?: number;\n ignoreOwner?: boolean;\n predictImpact?: boolean;\n}\n\nexport interface ClientProjectileImpact {\n id: string;\n targetId?: string;\n x: number;\n y: number;\n distance?: number;\n}\n\nexport interface ClientProjectileDestroy {\n id: string;\n reason?: string;\n targetId?: string;\n x?: number;\n y?: number;\n distance?: number;\n}\n\nexport interface RenderedProjectileProps extends ClientProjectileSpawn {\n x: number;\n y: number;\n angle: number;\n distance: number;\n elapsed: number;\n progress: number;\n impact?: ClientProjectileImpact;\n impactElapsed?: number;\n impactProgress?: number;\n destroyed?: boolean;\n}\n\nexport interface RenderedProjectile {\n id: string;\n type: string;\n component: any;\n props: RenderedProjectileProps;\n}\n\nexport type ProjectilePredictionResolver = (\n projectile: ClientProjectileSpawn,\n) => ClientProjectileImpact | null | undefined;\n\nexport interface ProjectileSpawnClock {\n now?: number;\n currentServerTick?: number;\n tickDurationMs?: number;\n mapId?: string;\n}\n\ninterface RuntimeProjectile {\n spawn: ClientProjectileSpawn;\n component: any;\n createdAt: number;\n impact?: ClientProjectileImpact;\n visualImpact?: ClientProjectileImpact;\n predictedImpact?: ClientProjectileImpact;\n impactStartedAt?: number;\n destroyAt?: number;\n destroyReason?: string;\n}\n\nexport class ProjectileManager {\n private readonly components = new Map<string, any>();\n private readonly projectiles = new Map<string, RuntimeProjectile>();\n private readonly version = signal(0);\n private readonly impactDurationMs = 350;\n private mapId?: string;\n\n constructor(\n private readonly hooks: Hooks,\n private readonly predictionResolver?: ProjectilePredictionResolver,\n ) {}\n\n current = computed<RenderedProjectile[]>(() => {\n this.version();\n const now = Date.now();\n const rendered: RenderedProjectile[] = [];\n for (const projectile of this.projectiles.values()) {\n const props = this.toProps(projectile, now);\n if (!props) {\n continue;\n }\n rendered.push({\n id: projectile.spawn.id,\n type: projectile.spawn.type,\n component: projectile.component,\n props,\n });\n }\n return rendered;\n });\n\n register(type: string, component: any): any {\n this.components.set(type, component);\n return component;\n }\n\n get(type: string): any {\n return this.components.get(type);\n }\n\n setMapId(mapId: string | undefined): void {\n const normalizedMapId = normalizeRoomMapId(mapId);\n if (this.mapId === normalizedMapId) return;\n this.mapId = normalizedMapId;\n this.clear();\n }\n\n getMapId(): string | undefined {\n return this.mapId;\n }\n\n spawnBatch(projectiles: ClientProjectileSpawn[], clock: ProjectileSpawnClock = {}): void {\n if (!this.acceptsMap(clock.mapId)) return;\n const now = clock.now ?? Date.now();\n for (const projectile of projectiles) {\n const component = this.components.get(projectile.type);\n if (!component) {\n continue;\n }\n const runtime: RuntimeProjectile = {\n spawn: {\n ...projectile,\n delay: projectile.delay ?? 0,\n index: projectile.index ?? 0,\n count: projectile.count ?? 1,\n },\n component,\n createdAt: now,\n };\n this.setPredictedImpact(runtime);\n this.projectiles.set(projectile.id, runtime);\n this.hooks.callHooks(\"client-projectiles-onSpawn\", runtime.spawn).subscribe();\n }\n this.touch();\n }\n\n impactBatch(impacts: ClientProjectileImpact[], context: { mapId?: string } = {}): void {\n if (!this.acceptsMap(context.mapId)) return;\n const now = Date.now();\n for (const impact of impacts) {\n const projectile = this.projectiles.get(impact.id);\n if (!projectile) {\n continue;\n }\n this.setImpact(projectile, impact, now);\n this.hooks.callHooks(\"client-projectiles-onImpact\", this.toProps(projectile, now)).subscribe();\n }\n this.touch();\n }\n\n destroyBatch(projectiles: ClientProjectileDestroy[], context: { mapId?: string } = {}): void {\n if (!this.acceptsMap(context.mapId)) return;\n const now = Date.now();\n for (const destroyed of projectiles) {\n const projectile = this.projectiles.get(destroyed.id);\n if (!projectile) {\n continue;\n }\n if (destroyed.reason === \"hit\") {\n const current = this.toProps(projectile, now);\n this.setImpact(projectile, {\n id: destroyed.id,\n targetId: destroyed.targetId ?? projectile.impact?.targetId,\n x: destroyed.x ?? projectile.impact?.x ?? current?.x ?? projectile.spawn.origin.x,\n y: destroyed.y ?? projectile.impact?.y ?? current?.y ?? projectile.spawn.origin.y,\n distance: destroyed.distance ?? projectile.impact?.distance ?? current?.distance,\n }, now);\n }\n projectile.destroyReason = destroyed.reason;\n projectile.destroyAt = projectile.destroyAt ?? (\n projectile.impact && projectile.impactStartedAt !== undefined\n ? projectile.impactStartedAt + this.impactDurationMs\n : now\n );\n this.hooks.callHooks(\"client-projectiles-onDestroy\", this.toProps(projectile, now)).subscribe();\n }\n this.touch();\n }\n\n clear(): void {\n this.projectiles.clear();\n this.touch();\n }\n\n step(): void {\n const now = Date.now();\n let changed = false;\n for (const [id, projectile] of this.projectiles) {\n const props = this.toProps(projectile, now);\n if (\n (!props && !this.isWaitingForDelay(projectile, now)) ||\n (projectile.destroyAt !== undefined && now >= projectile.destroyAt)\n ) {\n this.projectiles.delete(id);\n changed = true;\n }\n }\n this.touch(changed || this.projectiles.size > 0);\n }\n\n private toProps(projectile: RuntimeProjectile, now: number): RenderedProjectileProps | null {\n const spawn = projectile.spawn;\n const delayMs = (spawn.delay ?? 0) * 1000;\n const elapsedMs = now - projectile.createdAt - delayMs;\n if (elapsedMs < 0) {\n return null;\n }\n const elapsed = elapsedMs / 1000;\n const ttl = Math.max(0.001, spawn.ttl);\n const rawDistance = Math.min(spawn.speed * elapsed, spawn.range);\n const predictedImpact = this.getActivePredictedImpact(projectile, now, rawDistance);\n const visualImpact = projectile.visualImpact ?? projectile.impact;\n const distance = visualImpact?.distance ?? predictedImpact?.distance ?? rawDistance;\n const progress = Math.min(1, distance / spawn.range);\n const x = visualImpact?.x ?? predictedImpact?.x ?? spawn.origin.x + spawn.direction.x * distance;\n const y = visualImpact?.y ?? predictedImpact?.y ?? spawn.origin.y + spawn.direction.y * distance;\n const impactElapsedMs = projectile.impactStartedAt !== undefined\n ? Math.max(0, now - projectile.impactStartedAt)\n : undefined;\n return {\n ...spawn,\n x,\n y,\n angle: Math.atan2(spawn.direction.y, spawn.direction.x),\n distance,\n elapsed,\n progress,\n impact: projectile.impact,\n impactElapsed: impactElapsedMs === undefined ? undefined : impactElapsedMs / 1000,\n impactProgress: impactElapsedMs === undefined\n ? undefined\n : Math.min(1, impactElapsedMs / this.impactDurationMs),\n destroyed: projectile.destroyAt !== undefined,\n ttl,\n };\n }\n\n private acceptsMap(mapId: string | undefined): boolean {\n const normalizedMapId = normalizeRoomMapId(mapId);\n return !normalizedMapId || !this.mapId || normalizedMapId === this.mapId;\n }\n\n private isWaitingForDelay(projectile: RuntimeProjectile, now: number): boolean {\n const delayMs = (projectile.spawn.delay ?? 0) * 1000;\n return now - projectile.createdAt - delayMs < 0;\n }\n\n private setPredictedImpact(projectile: RuntimeProjectile): void {\n if (projectile.spawn.predictImpact === false) {\n return;\n }\n const impact = this.predictionResolver?.(projectile.spawn);\n if (!impact || !Number.isFinite(impact.x) || !Number.isFinite(impact.y)) {\n return;\n }\n const distance = typeof impact.distance === \"number\" && Number.isFinite(impact.distance)\n ? impact.distance\n : Math.hypot(impact.x - projectile.spawn.origin.x, impact.y - projectile.spawn.origin.y);\n if (!Number.isFinite(distance) || distance < 0 || distance > projectile.spawn.range) {\n return;\n }\n projectile.predictedImpact = {\n ...impact,\n distance,\n };\n }\n\n private getActivePredictedImpact(\n projectile: RuntimeProjectile,\n now: number,\n rawDistance: number,\n ): ClientProjectileImpact | undefined {\n if (!projectile.predictedImpact || projectile.impact) {\n return undefined;\n }\n const distance = projectile.predictedImpact.distance;\n if (distance === undefined || rawDistance < distance) {\n return undefined;\n }\n return projectile.predictedImpact;\n }\n\n private setImpact(projectile: RuntimeProjectile, impact: ClientProjectileImpact, now: number): void {\n projectile.visualImpact = this.resolveVisualImpact(projectile, impact, now);\n projectile.impact = impact;\n projectile.predictedImpact = undefined;\n projectile.impactStartedAt = projectile.impactStartedAt ?? now;\n const impactDestroyAt = projectile.impactStartedAt + this.impactDurationMs;\n projectile.destroyAt = Math.max(projectile.destroyAt ?? 0, impactDestroyAt);\n }\n\n private resolveVisualImpact(\n projectile: RuntimeProjectile,\n impact: ClientProjectileImpact,\n now: number,\n ): ClientProjectileImpact {\n const predicted = projectile.predictedImpact;\n if (!predicted || !this.isSameTarget(predicted, impact)) {\n return impact;\n }\n const distance = predicted.distance;\n if (distance === undefined) {\n return impact;\n }\n const delayMs = (projectile.spawn.delay ?? 0) * 1000;\n const elapsedMs = now - projectile.createdAt - delayMs;\n if (elapsedMs < 0) {\n return impact;\n }\n const rawDistance = Math.min(projectile.spawn.speed * (elapsedMs / 1000), projectile.spawn.range);\n return rawDistance >= distance ? predicted : impact;\n }\n\n private isSameTarget(a: ClientProjectileImpact, b: ClientProjectileImpact): boolean {\n return a.targetId !== undefined && a.targetId === b.targetId;\n }\n\n private touch(force = true): void {\n if (force) {\n this.version.update((value) => value + 1);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProjectileManager.js","names":[],"sources":["../../src/Game/ProjectileManager.ts"],"sourcesContent":["import { computed, signal } from \"canvasengine\";\nimport { Hooks } from \"@rpgjs/common\";\nimport { normalizeRoomMapId } from \"../utils/mapId\";\n\nexport interface ClientProjectileSpawn {\n id: string;\n type: string;\n ownerId?: string;\n origin: { x: number; y: number };\n direction: { x: number; y: number };\n speed: number;\n range: number;\n ttl: number;\n spawnTick: number;\n delay?: number;\n index?: number;\n count?: number;\n params?: Record<string, unknown>;\n collisionMask?: number;\n ignoreOwner?: boolean;\n predictImpact?: boolean;\n}\n\nexport interface ClientProjectileImpact {\n id: string;\n targetId?: string;\n x: number;\n y: number;\n distance?: number;\n}\n\nexport interface ClientProjectileDestroy {\n id: string;\n reason?: string;\n targetId?: string;\n x?: number;\n y?: number;\n distance?: number;\n}\n\nexport interface RenderedProjectileProps extends ClientProjectileSpawn {\n x: number;\n y: number;\n angle: number;\n distance: number;\n elapsed: number;\n progress: number;\n impact?: ClientProjectileImpact;\n impactElapsed?: number;\n impactProgress?: number;\n destroyed?: boolean;\n}\n\nexport interface RenderedProjectile {\n id: string;\n type: string;\n component: any;\n props: RenderedProjectileProps;\n}\n\nexport type ProjectilePredictionResolver = (\n projectile: ClientProjectileSpawn,\n) => ClientProjectileImpact | null | undefined;\n\nexport interface ProjectileSpawnClock {\n now?: number;\n currentServerTick?: number;\n tickDurationMs?: number;\n mapId?: string;\n}\n\ninterface RuntimeProjectile {\n spawn: ClientProjectileSpawn;\n component: any;\n createdAt: number;\n impact?: ClientProjectileImpact;\n visualImpact?: ClientProjectileImpact;\n predictedImpact?: ClientProjectileImpact;\n impactStartedAt?: number;\n destroyAt?: number;\n destroyReason?: string;\n}\n\nexport class ProjectileManager {\n private readonly components = new Map<string, any>();\n private readonly projectiles = new Map<string, RuntimeProjectile>();\n private readonly version = signal(0);\n private readonly impactDurationMs = 350;\n private mapId?: string;\n\n constructor(\n private readonly hooks: Hooks,\n private readonly predictionResolver?: ProjectilePredictionResolver,\n ) {}\n\n current = computed<RenderedProjectile[]>(() => {\n this.version();\n const now = Date.now();\n const rendered: RenderedProjectile[] = [];\n for (const projectile of this.projectiles.values()) {\n const props = this.toProps(projectile, now);\n if (!props) {\n continue;\n }\n rendered.push({\n id: projectile.spawn.id,\n type: projectile.spawn.type,\n component: projectile.component,\n props,\n });\n }\n return rendered;\n });\n\n register(type: string, component: any): any {\n this.components.set(type, component);\n return component;\n }\n\n get(type: string): any {\n return this.components.get(type);\n }\n\n setMapId(mapId: string | undefined): void {\n const normalizedMapId = normalizeRoomMapId(mapId);\n if (this.mapId === normalizedMapId) return;\n this.mapId = normalizedMapId;\n this.clear();\n }\n\n getMapId(): string | undefined {\n return this.mapId;\n }\n\n spawnBatch(projectiles: ClientProjectileSpawn[], clock: ProjectileSpawnClock = {}): void {\n if (!this.acceptsMap(clock.mapId)) return;\n const now = clock.now ?? Date.now();\n for (const projectile of projectiles) {\n const component = this.components.get(projectile.type);\n if (!component) {\n continue;\n }\n const runtime: RuntimeProjectile = {\n spawn: {\n ...projectile,\n delay: projectile.delay ?? 0,\n index: projectile.index ?? 0,\n count: projectile.count ?? 1,\n },\n component,\n createdAt: now,\n };\n this.setPredictedImpact(runtime);\n this.projectiles.set(projectile.id, runtime);\n this.hooks.callHooks(\"client-projectiles-onSpawn\", runtime.spawn).subscribe();\n }\n this.touch();\n }\n\n impactBatch(impacts: ClientProjectileImpact[], context: { mapId?: string } = {}): void {\n if (!this.acceptsMap(context.mapId)) return;\n const now = Date.now();\n for (const impact of impacts) {\n const projectile = this.projectiles.get(impact.id);\n if (!projectile) {\n continue;\n }\n this.setImpact(projectile, impact, now);\n this.hooks.callHooks(\"client-projectiles-onImpact\", this.toProps(projectile, now)).subscribe();\n }\n this.touch();\n }\n\n destroyBatch(projectiles: ClientProjectileDestroy[], context: { mapId?: string } = {}): void {\n if (!this.acceptsMap(context.mapId)) return;\n const now = Date.now();\n for (const destroyed of projectiles) {\n const projectile = this.projectiles.get(destroyed.id);\n if (!projectile) {\n continue;\n }\n if (destroyed.reason === \"hit\") {\n const current = this.toProps(projectile, now);\n this.setImpact(projectile, {\n id: destroyed.id,\n targetId: destroyed.targetId ?? projectile.impact?.targetId,\n x: destroyed.x ?? projectile.impact?.x ?? current?.x ?? projectile.spawn.origin.x,\n y: destroyed.y ?? projectile.impact?.y ?? current?.y ?? projectile.spawn.origin.y,\n distance: destroyed.distance ?? projectile.impact?.distance ?? current?.distance,\n }, now);\n }\n projectile.destroyReason = destroyed.reason;\n projectile.destroyAt = projectile.destroyAt ?? (\n projectile.impact && projectile.impactStartedAt !== undefined\n ? projectile.impactStartedAt + this.impactDurationMs\n : now\n );\n this.hooks.callHooks(\"client-projectiles-onDestroy\", this.toProps(projectile, now)).subscribe();\n }\n this.touch();\n }\n\n clear(): void {\n this.projectiles.clear();\n this.touch();\n }\n\n step(): void {\n const now = Date.now();\n let changed = false;\n for (const [id, projectile] of this.projectiles) {\n const props = this.toProps(projectile, now);\n if (\n (!props && !this.isWaitingForDelay(projectile, now)) ||\n (projectile.destroyAt !== undefined && now >= projectile.destroyAt)\n ) {\n this.projectiles.delete(id);\n changed = true;\n }\n }\n this.touch(changed || this.projectiles.size > 0);\n }\n\n private toProps(projectile: RuntimeProjectile, now: number): RenderedProjectileProps | null {\n const spawn = projectile.spawn;\n const delayMs = (spawn.delay ?? 0) * 1000;\n const elapsedMs = now - projectile.createdAt - delayMs;\n if (elapsedMs < 0) {\n return null;\n }\n const elapsed = elapsedMs / 1000;\n const ttl = Math.max(0.001, spawn.ttl);\n const rawDistance = Math.min(spawn.speed * elapsed, spawn.range);\n const predictedImpact = this.getActivePredictedImpact(projectile, now, rawDistance);\n const visualImpact = projectile.visualImpact ?? projectile.impact;\n const distance = visualImpact?.distance ?? predictedImpact?.distance ?? rawDistance;\n const progress = Math.min(1, distance / spawn.range);\n const x = visualImpact?.x ?? predictedImpact?.x ?? spawn.origin.x + spawn.direction.x * distance;\n const y = visualImpact?.y ?? predictedImpact?.y ?? spawn.origin.y + spawn.direction.y * distance;\n const impactElapsedMs = projectile.impactStartedAt !== undefined\n ? Math.max(0, now - projectile.impactStartedAt)\n : undefined;\n return {\n ...spawn,\n x,\n y,\n angle: Math.atan2(spawn.direction.y, spawn.direction.x),\n distance,\n elapsed,\n progress,\n impact: projectile.impact,\n impactElapsed: impactElapsedMs === undefined ? undefined : impactElapsedMs / 1000,\n impactProgress: impactElapsedMs === undefined\n ? undefined\n : Math.min(1, impactElapsedMs / this.impactDurationMs),\n destroyed: projectile.destroyAt !== undefined,\n ttl,\n };\n }\n\n private acceptsMap(mapId: string | undefined): boolean {\n const normalizedMapId = normalizeRoomMapId(mapId);\n return !normalizedMapId || !this.mapId || normalizedMapId === this.mapId;\n }\n\n private isWaitingForDelay(projectile: RuntimeProjectile, now: number): boolean {\n const delayMs = (projectile.spawn.delay ?? 0) * 1000;\n return now - projectile.createdAt - delayMs < 0;\n }\n\n private setPredictedImpact(projectile: RuntimeProjectile): void {\n if (projectile.spawn.predictImpact === false) {\n return;\n }\n const impact = this.predictionResolver?.(projectile.spawn);\n if (!impact || !Number.isFinite(impact.x) || !Number.isFinite(impact.y)) {\n return;\n }\n const distance = typeof impact.distance === \"number\" && Number.isFinite(impact.distance)\n ? impact.distance\n : Math.hypot(impact.x - projectile.spawn.origin.x, impact.y - projectile.spawn.origin.y);\n if (!Number.isFinite(distance) || distance < 0 || distance > projectile.spawn.range) {\n return;\n }\n projectile.predictedImpact = {\n ...impact,\n distance,\n };\n }\n\n private getActivePredictedImpact(\n projectile: RuntimeProjectile,\n now: number,\n rawDistance: number,\n ): ClientProjectileImpact | undefined {\n if (!projectile.predictedImpact || projectile.impact) {\n return undefined;\n }\n const distance = projectile.predictedImpact.distance;\n if (distance === undefined || rawDistance < distance) {\n return undefined;\n }\n return projectile.predictedImpact;\n }\n\n private setImpact(projectile: RuntimeProjectile, impact: ClientProjectileImpact, now: number): void {\n projectile.visualImpact = this.resolveVisualImpact(projectile, impact, now);\n projectile.impact = impact;\n projectile.predictedImpact = undefined;\n projectile.impactStartedAt = projectile.impactStartedAt ?? now;\n const impactDestroyAt = projectile.impactStartedAt + this.impactDurationMs;\n projectile.destroyAt = Math.max(projectile.destroyAt ?? 0, impactDestroyAt);\n }\n\n private resolveVisualImpact(\n projectile: RuntimeProjectile,\n impact: ClientProjectileImpact,\n now: number,\n ): ClientProjectileImpact {\n const predicted = projectile.predictedImpact;\n if (!predicted || !this.isSameTarget(predicted, impact)) {\n return impact;\n }\n const distance = predicted.distance;\n if (distance === undefined) {\n return impact;\n }\n const delayMs = (projectile.spawn.delay ?? 0) * 1000;\n const elapsedMs = now - projectile.createdAt - delayMs;\n if (elapsedMs < 0) {\n return impact;\n }\n const rawDistance = Math.min(projectile.spawn.speed * (elapsedMs / 1000), projectile.spawn.range);\n return rawDistance >= distance ? predicted : impact;\n }\n\n private isSameTarget(a: ClientProjectileImpact, b: ClientProjectileImpact): boolean {\n return a.targetId !== undefined && a.targetId === b.targetId;\n }\n\n private touch(force = true): void {\n if (force) {\n this.version.update((value) => value + 1);\n }\n }\n}\n"],"mappings":"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"}
package/dist/Gui/Gui.d.ts CHANGED
@@ -1,12 +1,19 @@
1
- import { Context } from '@signe/di';
2
- import { Signal, WritableSignal } from 'canvasengine';
1
+ import { Signal, WritableSignal, ComponentFunction } from 'canvasengine';
3
2
  import { Subscription } from 'rxjs';
4
- interface GuiOptions {
3
+ import { RpgContext } from '@rpgjs/common';
4
+ export type GuiRenderer = "canvas" | "vue";
5
+ export type GuiComponent = ComponentFunction | object;
6
+ export interface GuiRegistration<TData = unknown, TComponent extends GuiComponent = GuiComponent> {
5
7
  name?: string;
6
8
  id?: string;
7
- component?: any;
9
+ component: TComponent;
10
+ /**
11
+ * Renderer responsible for this component. CanvasEngine is the v5 default.
12
+ * `@rpgjs/vue` registers the official DOM overlay renderer.
13
+ */
14
+ renderer?: GuiRenderer;
8
15
  display?: boolean;
9
- data?: any;
16
+ data?: TData;
10
17
  /**
11
18
  * Auto display the GUI when added to the system
12
19
  * @default false
@@ -17,7 +24,7 @@ interface GuiOptions {
17
24
  * The GUI will only display when all dependencies are resolved (!= undefined)
18
25
  * @returns Array of Signal dependencies
19
26
  */
20
- dependencies?: () => Signal[];
27
+ dependencies?: () => Signal<unknown>[];
21
28
  /**
22
29
  * Attach the GUI to sprites instead of displaying globally
23
30
  * When true, the GUI will be rendered in character.ce for each sprite
@@ -29,17 +36,28 @@ interface GuiOptions {
29
36
  */
30
37
  rpgAttachToSprite?: boolean;
31
38
  }
32
- export interface GuiInstance {
39
+ export type GuiEntry<TData = unknown> = GuiRegistration<TData> | GuiComponent;
40
+ export interface GuiInstance<TData = unknown, TComponent extends GuiComponent = GuiComponent> {
33
41
  name: string;
34
- component: any;
42
+ component: TComponent;
43
+ renderer: GuiRenderer;
35
44
  display: WritableSignal<boolean>;
36
- data: WritableSignal<any>;
45
+ data: WritableSignal<TData>;
37
46
  openId?: string;
38
47
  autoDisplay: boolean;
39
- dependencies?: Signal[];
48
+ dependencies?: Signal<unknown>[];
40
49
  subscription?: Subscription;
41
50
  attachToSprite?: boolean;
42
51
  }
52
+ export type GuiRenderState<TData = unknown, TComponent extends GuiComponent = GuiComponent> = {
53
+ name: string;
54
+ component: TComponent;
55
+ renderer: GuiRenderer;
56
+ display: boolean;
57
+ data: TData;
58
+ openId?: string;
59
+ attachToSprite: boolean;
60
+ };
43
61
  interface GuiAction {
44
62
  guiId: string;
45
63
  name: string;
@@ -50,7 +68,7 @@ type OptimisticReducer = (data: any, action: GuiAction) => any;
50
68
  export declare class RpgGui {
51
69
  private context;
52
70
  private webSocket;
53
- gui: import('canvasengine').WritableObjectSignal<Record<string, GuiInstance>>;
71
+ gui: import('canvasengine').WritableObjectSignal<Record<string, GuiInstance<unknown, GuiComponent>>>;
54
72
  extraGuis: GuiInstance[];
55
73
  private vueGuiInstance;
56
74
  private optimisticReducers;
@@ -60,7 +78,7 @@ export declare class RpgGui {
60
78
  * Key: player ID, Value: boolean (true = show, false = hide)
61
79
  */
62
80
  attachedGuiDisplayState: import('canvasengine').WritableObjectSignal<Record<string, boolean>>;
63
- constructor(context: Context);
81
+ constructor(context: RpgContext);
64
82
  _initialize(): Promise<void>;
65
83
  /**
66
84
  * Set the VueGui instance reference for Vue component management
@@ -118,7 +136,7 @@ export declare class RpgGui {
118
136
  * });
119
137
  * ```
120
138
  */
121
- add(gui: GuiOptions | any): void;
139
+ add(gui: GuiEntry): void;
122
140
  registerOptimisticReducer(guiId: string, reducer: OptimisticReducer): void;
123
141
  /**
124
142
  * Get all attached GUI components (attachToSprite: true)
@@ -144,7 +162,7 @@ export declare class RpgGui {
144
162
  * @returns true if attached GUIs should be displayed for this player
145
163
  */
146
164
  shouldDisplayAttachedGui(playerId: string): boolean;
147
- get(id: string): GuiInstance | undefined;
165
+ get<TData = unknown>(id: string): GuiInstance<TData> | undefined;
148
166
  exists(id: string): boolean;
149
167
  getAll(): Record<string, GuiInstance>;
150
168
  /**
@@ -168,7 +186,7 @@ export declare class RpgGui {
168
186
  * gui.display('shop', { shopId: 1 }, [playerSignal, shopSignal]);
169
187
  * ```
170
188
  */
171
- display(id: string, data?: {}, dependencies?: Signal[], openId?: string): void;
189
+ display(id: string, data?: unknown, dependencies?: Signal<unknown>[], openId?: string): void;
172
190
  isDisplaying(id: string): boolean;
173
191
  /**
174
192
  * Handle Vue component display logic
@@ -197,8 +215,9 @@ export declare class RpgGui {
197
215
  private isVueComponentInstance;
198
216
  private removeCanvasGui;
199
217
  private removeVueGui;
200
- private resolveComponent;
201
- private resolveGuiId;
218
+ private resolveRegistration;
219
+ private resolveComponentName;
220
+ private resolveLegacyRenderer;
202
221
  private resolveAttachToSprite;
203
222
  private toGuiState;
204
223
  private clearPendingActions;