@rpgjs/client 5.0.0-beta.1 → 5.0.0-beta.11

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 (245) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/LICENSE +19 -0
  3. package/dist/Game/AnimationManager.d.ts +1 -1
  4. package/dist/Game/AnimationManager.js +18 -9
  5. package/dist/Game/AnimationManager.js.map +1 -1
  6. package/dist/Game/AnimationManager.spec.d.ts +1 -0
  7. package/dist/Game/Event.js.map +1 -1
  8. package/dist/Game/Map.d.ts +9 -1
  9. package/dist/Game/Map.js +63 -5
  10. package/dist/Game/Map.js.map +1 -1
  11. package/dist/Game/Object.d.ts +47 -15
  12. package/dist/Game/Object.js +82 -38
  13. package/dist/Game/Object.js.map +1 -1
  14. package/dist/Game/Player.js.map +1 -1
  15. package/dist/Game/ProjectileManager.d.ts +89 -0
  16. package/dist/Game/ProjectileManager.js +179 -0
  17. package/dist/Game/ProjectileManager.js.map +1 -0
  18. package/dist/Game/ProjectileManager.spec.d.ts +1 -0
  19. package/dist/Gui/Gui.d.ts +17 -4
  20. package/dist/Gui/Gui.js +78 -48
  21. package/dist/Gui/Gui.js.map +1 -1
  22. package/dist/Gui/Gui.spec.d.ts +1 -0
  23. package/dist/Gui/NotificationManager.js.map +1 -1
  24. package/dist/Resource.js +1 -1
  25. package/dist/Resource.js.map +1 -1
  26. package/dist/RpgClient.d.ts +110 -15
  27. package/dist/RpgClientEngine.d.ts +86 -10
  28. package/dist/RpgClientEngine.js +306 -49
  29. package/dist/RpgClientEngine.js.map +1 -1
  30. package/dist/Sound.js.map +1 -1
  31. package/dist/_virtual/{_@oxc-project_runtime@0.122.0 → _@oxc-project_runtime@0.130.0}/helpers/decorate.js +1 -1
  32. package/dist/_virtual/{_@oxc-project_runtime@0.122.0 → _@oxc-project_runtime@0.130.0}/helpers/decorateMetadata.js +1 -1
  33. package/dist/components/animations/animation.ce.js +4 -5
  34. package/dist/components/animations/animation.ce.js.map +1 -1
  35. package/dist/components/animations/hit.ce.js +19 -25
  36. package/dist/components/animations/hit.ce.js.map +1 -1
  37. package/dist/components/animations/index.js +4 -4
  38. package/dist/components/animations/index.js.map +1 -1
  39. package/dist/components/character.ce.js +422 -240
  40. package/dist/components/character.ce.js.map +1 -1
  41. package/dist/components/dynamics/bar.ce.js +97 -0
  42. package/dist/components/dynamics/bar.ce.js.map +1 -0
  43. package/dist/components/dynamics/image.ce.js +24 -0
  44. package/dist/components/dynamics/image.ce.js.map +1 -0
  45. package/dist/components/dynamics/parse-value.d.ts +3 -0
  46. package/dist/components/dynamics/parse-value.js +54 -35
  47. package/dist/components/dynamics/parse-value.js.map +1 -1
  48. package/dist/components/dynamics/parse-value.spec.d.ts +1 -0
  49. package/dist/components/dynamics/shape-utils.d.ts +16 -0
  50. package/dist/components/dynamics/shape-utils.js +73 -0
  51. package/dist/components/dynamics/shape-utils.js.map +1 -0
  52. package/dist/components/dynamics/shape-utils.spec.d.ts +1 -0
  53. package/dist/components/dynamics/shape.ce.js +84 -0
  54. package/dist/components/dynamics/shape.ce.js.map +1 -0
  55. package/dist/components/dynamics/text.ce.js +34 -56
  56. package/dist/components/dynamics/text.ce.js.map +1 -1
  57. package/dist/components/gui/box.ce.js +6 -8
  58. package/dist/components/gui/box.ce.js.map +1 -1
  59. package/dist/components/gui/dialogbox/index.ce.js +56 -62
  60. package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
  61. package/dist/components/gui/gameover.ce.js +42 -65
  62. package/dist/components/gui/gameover.ce.js.map +1 -1
  63. package/dist/components/gui/hud/hud.ce.js +21 -30
  64. package/dist/components/gui/hud/hud.ce.js.map +1 -1
  65. package/dist/components/gui/menu/equip-menu.ce.js +112 -165
  66. package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
  67. package/dist/components/gui/menu/exit-menu.ce.js +8 -6
  68. package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
  69. package/dist/components/gui/menu/items-menu.ce.js +52 -69
  70. package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
  71. package/dist/components/gui/menu/main-menu.ce.js +75 -92
  72. package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
  73. package/dist/components/gui/menu/options-menu.ce.js +5 -4
  74. package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
  75. package/dist/components/gui/menu/skills-menu.ce.js +12 -17
  76. package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
  77. package/dist/components/gui/mobile/index.js +2 -2
  78. package/dist/components/gui/mobile/index.js.map +1 -1
  79. package/dist/components/gui/mobile/mobile.ce.js +5 -4
  80. package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
  81. package/dist/components/gui/notification/notification.ce.js +22 -24
  82. package/dist/components/gui/notification/notification.ce.js.map +1 -1
  83. package/dist/components/gui/save-load.ce.js +72 -249
  84. package/dist/components/gui/save-load.ce.js.map +1 -1
  85. package/dist/components/gui/shop/shop.ce.js +90 -127
  86. package/dist/components/gui/shop/shop.ce.js.map +1 -1
  87. package/dist/components/gui/title-screen.ce.js +45 -70
  88. package/dist/components/gui/title-screen.ce.js.map +1 -1
  89. package/dist/components/index.d.ts +2 -1
  90. package/dist/components/index.js +1 -0
  91. package/dist/components/player-components-utils.d.ts +67 -0
  92. package/dist/components/player-components-utils.js +162 -0
  93. package/dist/components/player-components-utils.js.map +1 -0
  94. package/dist/components/player-components-utils.spec.d.ts +1 -0
  95. package/dist/components/player-components.ce.js +189 -0
  96. package/dist/components/player-components.ce.js.map +1 -0
  97. package/dist/components/prebuilt/hp-bar.ce.js +42 -44
  98. package/dist/components/prebuilt/hp-bar.ce.js.map +1 -1
  99. package/dist/components/prebuilt/light-halo.ce.js +36 -59
  100. package/dist/components/prebuilt/light-halo.ce.js.map +1 -1
  101. package/dist/components/scenes/canvas.ce.js +165 -21
  102. package/dist/components/scenes/canvas.ce.js.map +1 -1
  103. package/dist/components/scenes/draw-map.ce.js +25 -32
  104. package/dist/components/scenes/draw-map.ce.js.map +1 -1
  105. package/dist/components/scenes/event-layer.ce.js +9 -8
  106. package/dist/components/scenes/event-layer.ce.js.map +1 -1
  107. package/dist/core/inject.js +1 -1
  108. package/dist/core/inject.js.map +1 -1
  109. package/dist/core/setup.js +1 -1
  110. package/dist/core/setup.js.map +1 -1
  111. package/dist/decorators/spritesheet.d.ts +1 -0
  112. package/dist/decorators/spritesheet.js +11 -0
  113. package/dist/decorators/spritesheet.js.map +1 -0
  114. package/dist/index.d.ts +4 -0
  115. package/dist/index.js +26 -21
  116. package/dist/module.js +15 -1
  117. package/dist/module.js.map +1 -1
  118. package/dist/node_modules/.pnpm/{@signe_di@2.9.0 → @signe_di@3.0.1}/node_modules/@signe/di/dist/index.js +7 -117
  119. package/dist/node_modules/.pnpm/@signe_di@3.0.1/node_modules/@signe/di/dist/index.js.map +1 -0
  120. package/dist/node_modules/.pnpm/@signe_reactive@3.0.1/node_modules/@signe/reactive/dist/index.js +239 -0
  121. package/dist/node_modules/.pnpm/@signe_reactive@3.0.1/node_modules/@signe/reactive/dist/index.js.map +1 -0
  122. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/chunk-EUXUH3YW.js +13 -0
  123. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/chunk-EUXUH3YW.js.map +1 -0
  124. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/index.js +696 -0
  125. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/index.js.map +1 -0
  126. package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/client/index.js +44 -0
  127. package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/client/index.js.map +1 -0
  128. package/dist/node_modules/.pnpm/{@signe_sync@2.9.0 → @signe_sync@3.0.1}/node_modules/@signe/sync/dist/index.js +57 -141
  129. package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/index.js.map +1 -0
  130. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-HAC622V3.js.map +1 -1
  131. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-S74YV6PU.js.map +1 -1
  132. package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js +27 -27
  133. package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js.map +1 -1
  134. package/dist/presets/animation.js.map +1 -1
  135. package/dist/presets/faceset.js.map +1 -1
  136. package/dist/presets/icon.js.map +1 -1
  137. package/dist/presets/index.js.map +1 -1
  138. package/dist/presets/lpc.js.map +1 -1
  139. package/dist/presets/rmspritesheet.js.map +1 -1
  140. package/dist/services/AbstractSocket.js.map +1 -1
  141. package/dist/services/actionInput.d.ts +12 -0
  142. package/dist/services/actionInput.js +27 -0
  143. package/dist/services/actionInput.js.map +1 -0
  144. package/dist/services/actionInput.spec.d.ts +1 -0
  145. package/dist/services/keyboardControls.js.map +1 -1
  146. package/dist/services/loadMap.d.ts +6 -0
  147. package/dist/services/loadMap.js +1 -1
  148. package/dist/services/loadMap.js.map +1 -1
  149. package/dist/services/mmorpg-connection.d.ts +5 -0
  150. package/dist/services/mmorpg-connection.js +50 -0
  151. package/dist/services/mmorpg-connection.js.map +1 -0
  152. package/dist/services/mmorpg-connection.spec.d.ts +1 -0
  153. package/dist/services/mmorpg.d.ts +10 -4
  154. package/dist/services/mmorpg.js +56 -33
  155. package/dist/services/mmorpg.js.map +1 -1
  156. package/dist/services/pointerContext.d.ts +11 -0
  157. package/dist/services/pointerContext.js +48 -0
  158. package/dist/services/pointerContext.js.map +1 -0
  159. package/dist/services/pointerContext.spec.d.ts +1 -0
  160. package/dist/services/save.js.map +1 -1
  161. package/dist/services/save.spec.d.ts +1 -0
  162. package/dist/services/standalone-message.d.ts +1 -0
  163. package/dist/services/standalone-message.js +9 -0
  164. package/dist/services/standalone-message.js.map +1 -0
  165. package/dist/services/standalone.js +4 -3
  166. package/dist/services/standalone.js.map +1 -1
  167. package/dist/services/standalone.spec.d.ts +1 -0
  168. package/dist/utils/getEntityProp.js +4 -3
  169. package/dist/utils/getEntityProp.js.map +1 -1
  170. package/dist/utils/getEntityProp.spec.d.ts +1 -0
  171. package/dist/utils/readPropValue.d.ts +2 -0
  172. package/dist/utils/readPropValue.js +13 -0
  173. package/dist/utils/readPropValue.js.map +1 -0
  174. package/package.json +13 -14
  175. package/src/Game/AnimationManager.spec.ts +30 -0
  176. package/src/Game/AnimationManager.ts +22 -10
  177. package/src/Game/Map.ts +91 -2
  178. package/src/Game/Object.ts +148 -69
  179. package/src/Game/ProjectileManager.spec.ts +338 -0
  180. package/src/Game/ProjectileManager.ts +324 -0
  181. package/src/Gui/Gui.spec.ts +273 -0
  182. package/src/Gui/Gui.ts +105 -50
  183. package/src/Resource.ts +1 -2
  184. package/src/RpgClient.ts +125 -17
  185. package/src/RpgClientEngine.ts +457 -87
  186. package/src/components/character.ce +441 -32
  187. package/src/components/dynamics/bar.ce +88 -0
  188. package/src/components/dynamics/image.ce +21 -0
  189. package/src/components/dynamics/parse-value.spec.ts +83 -0
  190. package/src/components/dynamics/parse-value.ts +111 -37
  191. package/src/components/dynamics/shape-utils.spec.ts +46 -0
  192. package/src/components/dynamics/shape-utils.ts +61 -0
  193. package/src/components/dynamics/shape.ce +90 -0
  194. package/src/components/dynamics/text.ce +35 -149
  195. package/src/components/gui/dialogbox/index.ce +18 -8
  196. package/src/components/gui/gameover.ce +2 -1
  197. package/src/components/gui/menu/equip-menu.ce +2 -1
  198. package/src/components/gui/menu/exit-menu.ce +2 -1
  199. package/src/components/gui/menu/items-menu.ce +3 -2
  200. package/src/components/gui/menu/main-menu.ce +2 -1
  201. package/src/components/gui/save-load.ce +2 -1
  202. package/src/components/gui/shop/shop.ce +3 -2
  203. package/src/components/gui/title-screen.ce +2 -1
  204. package/src/components/index.ts +2 -1
  205. package/src/components/player-components-utils.spec.ts +109 -0
  206. package/src/components/player-components-utils.ts +205 -0
  207. package/src/components/player-components.ce +222 -0
  208. package/src/components/prebuilt/hp-bar.ce +4 -3
  209. package/src/components/prebuilt/light-halo.ce +2 -2
  210. package/src/components/scenes/canvas.ce +175 -8
  211. package/src/components/scenes/draw-map.ce +18 -17
  212. package/src/components/scenes/event-layer.ce +1 -2
  213. package/src/core/setup.ts +2 -2
  214. package/src/decorators/spritesheet.ts +8 -0
  215. package/src/index.ts +4 -0
  216. package/src/module.ts +18 -1
  217. package/src/services/actionInput.spec.ts +101 -0
  218. package/src/services/actionInput.ts +53 -0
  219. package/src/services/loadMap.ts +2 -0
  220. package/src/services/mmorpg-connection.spec.ts +99 -0
  221. package/src/services/mmorpg-connection.ts +69 -0
  222. package/src/services/mmorpg.ts +68 -36
  223. package/src/services/pointerContext.spec.ts +36 -0
  224. package/src/services/pointerContext.ts +84 -0
  225. package/src/services/save.spec.ts +127 -0
  226. package/src/services/standalone-message.ts +7 -0
  227. package/src/services/standalone.spec.ts +34 -0
  228. package/src/services/standalone.ts +3 -2
  229. package/src/utils/getEntityProp.spec.ts +96 -0
  230. package/src/utils/getEntityProp.ts +4 -3
  231. package/src/utils/readPropValue.ts +16 -0
  232. package/dist/node_modules/.pnpm/@signe_di@2.9.0/node_modules/@signe/di/dist/index.js.map +0 -1
  233. package/dist/node_modules/.pnpm/@signe_reactive@2.8.3/node_modules/@signe/reactive/dist/index.js +0 -457
  234. package/dist/node_modules/.pnpm/@signe_reactive@2.8.3/node_modules/@signe/reactive/dist/index.js.map +0 -1
  235. package/dist/node_modules/.pnpm/@signe_reactive@2.9.0/node_modules/@signe/reactive/dist/index.js +0 -463
  236. package/dist/node_modules/.pnpm/@signe_reactive@2.9.0/node_modules/@signe/reactive/dist/index.js.map +0 -1
  237. package/dist/node_modules/.pnpm/@signe_room@2.9.0/node_modules/@signe/room/dist/index.js +0 -2191
  238. package/dist/node_modules/.pnpm/@signe_room@2.9.0/node_modules/@signe/room/dist/index.js.map +0 -1
  239. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/chunk-7QVYU63E.js +0 -10
  240. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/chunk-7QVYU63E.js.map +0 -1
  241. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/client/index.js +0 -91
  242. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/client/index.js.map +0 -1
  243. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/index.js.map +0 -1
  244. package/dist/node_modules/.pnpm/dset@3.1.4/node_modules/dset/dist/index.js +0 -14
  245. package/dist/node_modules/.pnpm/dset@3.1.4/node_modules/dset/dist/index.js.map +0 -1
@@ -1,30 +1,38 @@
1
1
  <Container x={smoothX} y={smoothY} zIndex={z} viewportFollow={shouldFollowCamera} controls onBeforeDestroy visible>
2
2
  @for (compConfig of normalizedComponentsBehind) {
3
3
  <Container>
4
- <compConfig.component object ...compConfig.props />
4
+ <compConfig.component object={sprite} ...compConfig.props />
5
5
  </Container>
6
6
  }
7
+ <PlayerComponents object={sprite} position="bottom" graphicBounds />
8
+ <PlayerComponents object={sprite} position="left" graphicBounds />
7
9
  <Particle emit={emitParticleTrigger} settings={particleSettings} zIndex={1000} name={particleName} />
8
10
  <Container>
9
11
  @for (graphicObj of graphicsSignals) {
10
- <Sprite
11
- sheet={sheet(graphicObj)}
12
- direction
13
- tint
14
- hitbox
15
- flash={flashConfig}
16
- />
12
+ <Container scale={graphicScale(graphicObj)}>
13
+ <Sprite
14
+ sheet={sheet(graphicObj)}
15
+ direction
16
+ tint
17
+ hitbox
18
+ shadowCaster={shadowCaster(graphicObj)}
19
+ flash={flashConfig}
20
+ />
21
+ </Container>
17
22
  }
18
23
  </Container>
24
+ <PlayerComponents object={sprite} position="center" graphicBounds />
25
+ <PlayerComponents object={sprite} position="right" graphicBounds />
26
+ <PlayerComponents object={sprite} position="top" graphicBounds />
19
27
  @for (compConfig of normalizedComponentsInFront) {
20
28
  <Container dependencies={compConfig.dependencies}>
21
- <compConfig.component object ...compConfig.props />
29
+ <compConfig.component object={sprite} ...compConfig.props />
22
30
  </Container>
23
31
  }
24
32
  @for (attachedGui of attachedGuis) {
25
33
  @if (shouldDisplayAttachedGui) {
26
34
  <Container>
27
- <attachedGui.component ...attachedGui.data() dependencies={attachedGui.dependencies} object={object} onFinish={(data) => {
35
+ <attachedGui.component ...attachedGui.data() dependencies={attachedGui.dependencies} object={sprite} onFinish={(data) => {
28
36
  onAttachedGuiFinish(attachedGui, data)
29
37
  }} onInteraction={(name, data) => {
30
38
  onAttachedGuiInteraction(attachedGui, name, data)
@@ -36,28 +44,44 @@
36
44
 
37
45
  <script>
38
46
  import { signal, effect, mount, computed, tick, animatedSignal, on } from "canvasengine";
47
+ import { Assets } from "pixi.js";
39
48
 
40
49
  import { lastValueFrom, combineLatest, pairwise, filter, map, startWith } from "rxjs";
41
50
  import { Particle } from "@canvasengine/presets";
42
51
  import { GameEngineToken, ModulesToken } from "@rpgjs/common";
43
52
  import { RpgClientEngine } from "../RpgClientEngine";
44
53
  import { inject } from "../core/inject";
45
- import { Direction } from "@rpgjs/common";
54
+ import { Direction, Animation } from "@rpgjs/common";
46
55
  import Hit from "./effects/hit.ce";
47
56
  import PlayerComponents from "./player-components.ce";
48
57
  import { RpgGui } from "../Gui/Gui";
58
+ import { getCanMoveValue } from "../utils/readPropValue";
59
+ import { getKeyboardControlBind, resolveKeyboardActionInput } from "../services/actionInput";
49
60
 
50
61
  const { object, id } = defineProps();
62
+ const sprite = object();
51
63
 
52
64
  const client = inject(RpgClientEngine);
53
65
  const hooks = inject(ModulesToken);
54
66
  const guiService = inject(RpgGui);
55
67
 
56
68
  const spritesheets = client.spritesheets;
57
- const playerId = client.playerId;
58
69
  const componentsBehind = client.spriteComponentsBehind;
59
70
  const componentsInFront = client.spriteComponentsInFront;
60
- const isMe = computed(() => id() === playerId);
71
+ const readProp = (value) => typeof value === 'function' ? value() : value;
72
+ const isCurrentPlayer = () => {
73
+ const playerId = client.playerIdSignal();
74
+ const currentPlayer = playerId ? client.sceneMap?.players?.()?.[playerId] : undefined;
75
+ return readProp(id) === playerId
76
+ || readProp(sprite?.id) === playerId
77
+ || sprite === currentPlayer
78
+ || sprite === client.sceneMap?.getCurrentPlayer?.();
79
+ };
80
+ const isMe = computed(isCurrentPlayer);
81
+ const shadowsEnabled = computed(() => {
82
+ const lighting = client.sceneMap?.lighting?.();
83
+ return Boolean(lighting?.shadows?.enabled || (lighting?.spots?.length ?? 0) > 0);
84
+ });
61
85
 
62
86
  /**
63
87
  * Normalize a single sprite component configuration
@@ -128,8 +152,8 @@
128
152
  // The computed will be created in the template when needed
129
153
  return {
130
154
  component: componentRef,
131
- props: typeof propsValue === 'function' ? propsValue(object) : propsValue || {},
132
- dependencies: dependenciesFn ? dependenciesFn(object) : []
155
+ props: typeof propsValue === 'function' ? propsValue(sprite) : propsValue || {},
156
+ dependencies: dependenciesFn ? dependenciesFn(sprite) : []
133
157
  };
134
158
  };
135
159
 
@@ -237,7 +261,7 @@
237
261
  isConnected,
238
262
  graphicsSignals,
239
263
  flashTrigger
240
- } = object;
264
+ } = sprite;
241
265
 
242
266
  /**
243
267
  * Flash configuration signals for dynamic options
@@ -282,17 +306,17 @@
282
306
 
283
307
  const particleSettings = client.particleSettings;
284
308
 
285
- const canControls = () => isMe() && object.canMove()
309
+ const canControls = () => isMe() && getCanMoveValue(sprite)
286
310
  const keyboardControls = client.globalConfig.keyboardControls;
287
311
 
288
312
  const visible = computed(() => {
289
- if (object.isEvent()) {
313
+ if (sprite.isEvent()) {
290
314
  return true
291
315
  }
292
316
  return isConnected()
293
317
  });
294
318
 
295
- const controls = signal({
319
+ const controls = {
296
320
  down: {
297
321
  repeat: true,
298
322
  bind: keyboardControls.down,
@@ -322,10 +346,10 @@
322
346
  },
323
347
  },
324
348
  action: {
325
- bind: keyboardControls.action,
349
+ bind: getKeyboardControlBind(keyboardControls.action),
326
350
  keyDown() {
327
351
  if (canControls()) {
328
- client.processAction({ action: 'action' })
352
+ client.processAction(resolveKeyboardActionInput(keyboardControls.action, client, sprite))
329
353
  }
330
354
  },
331
355
  },
@@ -340,7 +364,7 @@
340
364
  gamepad: {
341
365
  enabled: true
342
366
  }
343
- });
367
+ };
344
368
 
345
369
  const smoothX = animatedSignal(x(), {
346
370
  duration: isMe() ? 0 : 0
@@ -351,7 +375,7 @@
351
375
  });
352
376
 
353
377
  const z = computed(() => {
354
- return object.y() + object.z()
378
+ return sprite.y() + sprite.z()
355
379
  });
356
380
 
357
381
  const realAnimationName = signal(animationName());
@@ -377,6 +401,315 @@
377
401
  };
378
402
  }
379
403
 
404
+ const graphicScale = (graphicObject) => {
405
+ const scale = graphicObject?.scale;
406
+ if (Array.isArray(scale)) return scale;
407
+ if (typeof scale === 'number') return [scale, scale];
408
+ if (scale && typeof scale === 'object') {
409
+ const x = typeof scale.x === 'number' ? scale.x : 1;
410
+ const y = typeof scale.y === 'number' ? scale.y : x;
411
+ return [x, y];
412
+ }
413
+ return undefined;
414
+ }
415
+
416
+ const shadowCaster = (graphicObject) => {
417
+ const box = hitbox();
418
+ const bounds = graphicBounds();
419
+ const scale = graphicScale(graphicObject);
420
+ const scaleY = Array.isArray(scale) && typeof scale[1] === 'number' ? Math.abs(scale[1]) : 1;
421
+ const height = Math.max(bounds?.height ?? box?.h ?? 32, box?.h ?? 32) * scaleY;
422
+
423
+ return {
424
+ enabled: shadowsEnabled,
425
+ height,
426
+ footAnchor: { x: 0.5, y: 1 },
427
+ footOffset: { x: 0, y: 2 },
428
+ alpha: 0.5,
429
+ blur: 3.5,
430
+ gradientPower: 2,
431
+ hardness: 0.42,
432
+ minLength: Math.max(6, (box?.h ?? 32) * 0.25),
433
+ maxLength: Math.max(90, height * 1.8),
434
+ contactAlpha: 0.3,
435
+ contactScale: 0.3,
436
+ };
437
+ }
438
+
439
+ const imageDimensions = signal({});
440
+ const loadingImageDimensions = new Set();
441
+
442
+ const toPositiveNumber = (value) => {
443
+ const number = typeof value === 'number' ? value : parseFloat(value);
444
+ return Number.isFinite(number) && number > 0 ? number : undefined;
445
+ };
446
+
447
+ const toFiniteNumber = (value, fallback = 0) => {
448
+ const number = typeof value === 'number' ? value : parseFloat(value);
449
+ return Number.isFinite(number) ? number : fallback;
450
+ };
451
+
452
+ const clampRatio = (value) => Math.min(1, Math.max(0, value));
453
+
454
+ const normalizePair = (value, fallback = [1, 1]) => {
455
+ if (Array.isArray(value)) {
456
+ const x = toFiniteNumber(value[0], fallback[0]);
457
+ const y = toFiniteNumber(value[1] ?? value[0], x);
458
+ return [x, y];
459
+ }
460
+ if (typeof value === 'number') {
461
+ return [value, value];
462
+ }
463
+ if (value && typeof value === 'object') {
464
+ const x = toFiniteNumber(value.x, fallback[0]);
465
+ const y = toFiniteNumber(value.y ?? value.x, x);
466
+ return [x, y];
467
+ }
468
+ return fallback;
469
+ };
470
+
471
+ const normalizeAnchor = (value) => {
472
+ if (!Array.isArray(value)) return undefined;
473
+ const [x, y] = normalizePair(value, [0, 0]);
474
+ return [clampRatio(x), clampRatio(y)];
475
+ };
476
+
477
+ const resolveImageSource = (image) => {
478
+ if (typeof image === 'string') return image;
479
+ if (typeof image?.default === 'string') return image.default;
480
+ return undefined;
481
+ };
482
+
483
+ const parentTextureOptions = (graphicObject) => {
484
+ const props = [
485
+ 'width',
486
+ 'height',
487
+ 'framesHeight',
488
+ 'framesWidth',
489
+ 'rectWidth',
490
+ 'rectHeight',
491
+ 'offset',
492
+ 'image',
493
+ 'sound',
494
+ 'spriteRealSize',
495
+ 'scale',
496
+ 'anchor',
497
+ 'pivot',
498
+ 'x',
499
+ 'y',
500
+ 'opacity'
501
+ ];
502
+
503
+ return props.reduce((options, prop) => {
504
+ if (graphicObject?.[prop] !== undefined) {
505
+ options[prop] = graphicObject[prop];
506
+ }
507
+ return options;
508
+ }, {});
509
+ };
510
+
511
+ const resolveTextureOptions = (graphicObject) => {
512
+ const textures = graphicObject?.textures ?? {};
513
+ const texture =
514
+ textures[realAnimationName()] ??
515
+ textures[Animation.Stand] ??
516
+ Object.values(textures)[0] ??
517
+ {};
518
+
519
+ return {
520
+ ...parentTextureOptions(graphicObject),
521
+ ...texture
522
+ };
523
+ };
524
+
525
+ const resolveFirstAnimationFrame = (textureOptions) => {
526
+ const animations = textureOptions?.animations;
527
+ if (!animations) return {};
528
+
529
+ try {
530
+ const frames = typeof animations === 'function'
531
+ ? animations({ direction: direction() })
532
+ : animations;
533
+ if (!Array.isArray(frames)) return {};
534
+ const firstGroup = frames[0];
535
+ return Array.isArray(firstGroup) ? firstGroup[0] ?? {} : firstGroup ?? {};
536
+ }
537
+ catch {
538
+ return {};
539
+ }
540
+ };
541
+
542
+ const optionValue = (prop, frame, textureOptions, graphicObject) => {
543
+ return frame?.[prop] ?? textureOptions?.[prop] ?? graphicObject?.[prop];
544
+ };
545
+
546
+ const resolveFrameSize = (textureOptions, dimensions) => {
547
+ const framesWidth = toPositiveNumber(textureOptions?.framesWidth) ?? 1;
548
+ const framesHeight = toPositiveNumber(textureOptions?.framesHeight) ?? 1;
549
+ const imageSource = resolveImageSource(textureOptions?.image);
550
+ const loadedSize = imageSource ? dimensions[imageSource] : undefined;
551
+ const fullWidth = toPositiveNumber(textureOptions?.width) ?? loadedSize?.width;
552
+ const fullHeight = toPositiveNumber(textureOptions?.height) ?? loadedSize?.height;
553
+ const width = toPositiveNumber(textureOptions?.rectWidth) ??
554
+ toPositiveNumber(textureOptions?.spriteWidth) ??
555
+ (fullWidth ? fullWidth / framesWidth : undefined);
556
+ const height = toPositiveNumber(textureOptions?.rectHeight) ??
557
+ toPositiveNumber(textureOptions?.spriteHeight) ??
558
+ (fullHeight ? fullHeight / framesHeight : undefined);
559
+
560
+ return {
561
+ width,
562
+ height
563
+ };
564
+ };
565
+
566
+ const resolveHitboxAnchor = (spriteWidth, spriteHeight, realSize, box) => {
567
+ if (!spriteWidth || !spriteHeight || !box) {
568
+ return [0, 0];
569
+ }
570
+
571
+ const heightOfSprite = typeof realSize === 'number' ? realSize : realSize?.height;
572
+ const resolvedHeight = toPositiveNumber(heightOfSprite) ?? spriteHeight;
573
+ const gap = Math.max(0, (spriteHeight - resolvedHeight) / 2);
574
+ const hitboxTopLeftX = clampRatio((spriteWidth - box.w) / 2 / spriteWidth);
575
+ const hitboxTopLeftY = clampRatio((spriteHeight - box.h - gap) / spriteHeight);
576
+ const hitboxCenterX = clampRatio(hitboxTopLeftX + box.w / 2 / spriteWidth);
577
+ const hitboxCenterY = clampRatio(hitboxTopLeftY + box.h / 2 / spriteHeight);
578
+ const footY = clampRatio((spriteHeight - gap) / spriteHeight);
579
+
580
+ switch (box.anchorMode ?? 'top-left') {
581
+ case 'center':
582
+ return [hitboxCenterX, hitboxCenterY];
583
+ case 'foot':
584
+ return [hitboxCenterX, footY];
585
+ case 'top-left':
586
+ default:
587
+ return [hitboxTopLeftX, hitboxTopLeftY];
588
+ }
589
+ };
590
+
591
+ const loadImageDimensions = (image) => {
592
+ const source = resolveImageSource(image);
593
+ if (!source || imageDimensions()[source] || loadingImageDimensions.has(source)) {
594
+ return;
595
+ }
596
+
597
+ loadingImageDimensions.add(source);
598
+ Assets.load(source)
599
+ .then((texture) => {
600
+ const width = toPositiveNumber(texture?.width);
601
+ const height = toPositiveNumber(texture?.height);
602
+ if (!width || !height) return;
603
+
604
+ imageDimensions.update((dimensions) => ({
605
+ ...dimensions,
606
+ [source]: { width, height }
607
+ }));
608
+ })
609
+ .catch(() => {})
610
+ .finally(() => {
611
+ loadingImageDimensions.delete(source);
612
+ });
613
+ };
614
+
615
+ effect(() => {
616
+ const sources = new Set();
617
+
618
+ graphicsSignals().forEach((graphicObject) => {
619
+ const baseImage = resolveImageSource(graphicObject?.image);
620
+ if (baseImage) sources.add(baseImage);
621
+
622
+ Object.values(graphicObject?.textures ?? {}).forEach((textureOptions) => {
623
+ const image = resolveImageSource(textureOptions?.image ?? graphicObject?.image);
624
+ if (image) sources.add(image);
625
+ });
626
+ });
627
+
628
+ sources.forEach((source) => loadImageDimensions(source));
629
+ });
630
+
631
+ const hitboxBounds = computed(() => {
632
+ const box = hitbox();
633
+ const width = box?.w ?? 0;
634
+ const height = box?.h ?? 0;
635
+
636
+ return {
637
+ left: 0,
638
+ top: 0,
639
+ right: width,
640
+ bottom: height,
641
+ width,
642
+ height,
643
+ centerX: width / 2,
644
+ centerY: height / 2
645
+ };
646
+ });
647
+
648
+ const graphicBounds = computed(() => {
649
+ const box = hitbox();
650
+ const fallback = hitboxBounds();
651
+ const dimensions = imageDimensions();
652
+ const graphics = graphicsSignals();
653
+ let bounds = null;
654
+
655
+ graphics.forEach((graphicObject) => {
656
+ const textureOptions = resolveTextureOptions(graphicObject);
657
+ const frame = resolveFirstAnimationFrame(textureOptions);
658
+ const size = resolveFrameSize(textureOptions, dimensions);
659
+ const spriteWidth = size.width ?? box?.w;
660
+ const spriteHeight = size.height ?? box?.h;
661
+
662
+ if (!spriteWidth || !spriteHeight) {
663
+ return;
664
+ }
665
+
666
+ const explicitAnchor = normalizeAnchor(optionValue('anchor', frame, textureOptions, graphicObject));
667
+ const anchor = explicitAnchor ?? resolveHitboxAnchor(
668
+ spriteWidth,
669
+ spriteHeight,
670
+ optionValue('spriteRealSize', frame, textureOptions, graphicObject),
671
+ box
672
+ );
673
+ const scale = normalizePair(optionValue('scale', frame, textureOptions, graphicObject) ?? graphicScale(graphicObject));
674
+ const x = toFiniteNumber(optionValue('x', frame, textureOptions, graphicObject), 0);
675
+ const y = toFiniteNumber(optionValue('y', frame, textureOptions, graphicObject), 0);
676
+ const leftEdge = -anchor[0] * spriteWidth * scale[0];
677
+ const rightEdge = (1 - anchor[0]) * spriteWidth * scale[0];
678
+ const topEdge = -anchor[1] * spriteHeight * scale[1];
679
+ const bottomEdge = (1 - anchor[1]) * spriteHeight * scale[1];
680
+ const graphic = {
681
+ left: x + Math.min(leftEdge, rightEdge),
682
+ top: y + Math.min(topEdge, bottomEdge),
683
+ right: x + Math.max(leftEdge, rightEdge),
684
+ bottom: y + Math.max(topEdge, bottomEdge)
685
+ };
686
+
687
+ bounds = bounds
688
+ ? {
689
+ left: Math.min(bounds.left, graphic.left),
690
+ top: Math.min(bounds.top, graphic.top),
691
+ right: Math.max(bounds.right, graphic.right),
692
+ bottom: Math.max(bounds.bottom, graphic.bottom)
693
+ }
694
+ : graphic;
695
+ });
696
+
697
+ if (!bounds) {
698
+ return fallback;
699
+ }
700
+
701
+ const width = bounds.right - bounds.left;
702
+ const height = bounds.bottom - bounds.top;
703
+
704
+ return {
705
+ ...bounds,
706
+ width,
707
+ height,
708
+ centerX: bounds.left + width / 2,
709
+ centerY: bounds.top + height / 2
710
+ };
711
+ });
712
+
380
713
  // Combine animation change detection with movement state from smoothX/smoothY
381
714
  const movementAnimations = ['walk', 'stand'];
382
715
  const epsilon = 0; // movement threshold to consider the easing still running
@@ -400,6 +733,51 @@
400
733
  filter(([prev, curr]) => prev !== curr)
401
734
  );
402
735
 
736
+ let beforeRemovePromise = null;
737
+ let beforeRemoveTransitionValue = null;
738
+ const resolveRemoveContext = () => {
739
+ if (!sprite._removeTransition) return null;
740
+ const value = sprite._removeTransition();
741
+ if (!value || typeof value !== 'string') return null;
742
+ try {
743
+ const context = JSON.parse(value);
744
+ if (!context || typeof context !== 'object' || !context.active) return null;
745
+ context.__transitionValue = value;
746
+ return context;
747
+ }
748
+ catch {
749
+ return null;
750
+ }
751
+ };
752
+
753
+ const withTimeout = (promise, timeoutMs = 0) => {
754
+ if (!timeoutMs || timeoutMs <= 0) return promise;
755
+ return Promise.race([
756
+ promise,
757
+ new Promise((resolve) => setTimeout(resolve, timeoutMs)),
758
+ ]);
759
+ };
760
+
761
+ const runBeforeRemove = () => {
762
+ const context = resolveRemoveContext();
763
+ if (!context) return Promise.resolve();
764
+ if (beforeRemovePromise && beforeRemoveTransitionValue === context.__transitionValue) {
765
+ return beforeRemovePromise;
766
+ }
767
+ beforeRemoveTransitionValue = context.__transitionValue;
768
+ beforeRemovePromise = withTimeout(
769
+ lastValueFrom(hooks.callHooks("client-sprite-onBeforeRemove", sprite, context)),
770
+ context.timeoutMs
771
+ );
772
+ return beforeRemovePromise;
773
+ };
774
+
775
+ const removeTransitionSubscription = sprite._removeTransition?.observable?.subscribe(() => {
776
+ if (resolveRemoveContext()) {
777
+ runBeforeRemove();
778
+ }
779
+ });
780
+
403
781
  const animationMovementSubscription = combineLatest([animationChange$, moving$]).subscribe(([[prev, curr], isMoving]) => {
404
782
  if (curr == 'stand' && !isMoving) {
405
783
  realAnimationName.set(curr);
@@ -410,10 +788,10 @@
410
788
  else if (!movementAnimations.includes(curr)) {
411
789
  realAnimationName.set(curr);
412
790
  }
413
- if (!isMoving && object.animationIsPlaying && object.animationIsPlaying()) {
791
+ if (!isMoving && sprite.animationIsPlaying && sprite.animationIsPlaying()) {
414
792
  if (movementAnimations.includes(curr)) {
415
- if (typeof object.resetAnimationState === 'function') {
416
- object.resetAnimationState();
793
+ if (typeof sprite.resetAnimationState === 'function') {
794
+ sprite.resetAnimationState();
417
795
  }
418
796
  }
419
797
  }
@@ -429,18 +807,49 @@
429
807
  * @example
430
808
  * await onBeforeDestroy();
431
809
  */
810
+ const waitForTemporaryAnimationEnd = (maxDuration = 1200) => {
811
+ if (!sprite.animationIsPlaying || !sprite.animationIsPlaying()) {
812
+ return Promise.resolve();
813
+ }
814
+
815
+ return new Promise((resolve) => {
816
+ let finished = false;
817
+ let timeout;
818
+ let subscription;
819
+ const finish = () => {
820
+ if (finished) return;
821
+ finished = true;
822
+ clearTimeout(timeout);
823
+ subscription?.unsubscribe();
824
+ resolve();
825
+ };
826
+ timeout = setTimeout(finish, maxDuration);
827
+ subscription = sprite.animationIsPlaying.observable.subscribe((isPlaying) => {
828
+ if (!isPlaying) finish();
829
+ });
830
+ if (finished) subscription.unsubscribe();
831
+ });
832
+ };
833
+
432
834
  const onBeforeDestroy = async () => {
835
+ await runBeforeRemove();
836
+ await waitForTemporaryAnimationEnd();
837
+ removeTransitionSubscription?.unsubscribe();
433
838
  animationMovementSubscription.unsubscribe();
434
839
  xSubscription.unsubscribe();
435
840
  ySubscription.unsubscribe();
436
- await lastValueFrom(hooks.callHooks("client-sprite-onDestroy", object))
437
- await lastValueFrom(hooks.callHooks("client-sceneMap-onRemoveSprite", client.sceneMap, object))
841
+ await lastValueFrom(hooks.callHooks("client-sprite-onDestroy", sprite))
842
+ await lastValueFrom(hooks.callHooks("client-sceneMap-onRemoveSprite", client.sceneMap, sprite))
438
843
  }
439
844
 
440
845
  mount((element) => {
441
- hooks.callHooks("client-sprite-onAdd", object).subscribe()
442
- hooks.callHooks("client-sceneMap-onAddSprite", client.sceneMap, object).subscribe()
443
- if (isMe()) client.setKeyboardControls(element.directives.controls)
846
+ hooks.callHooks("client-sprite-onAdd", sprite).subscribe()
847
+ hooks.callHooks("client-sceneMap-onAddSprite", client.sceneMap, sprite).subscribe()
848
+ effect(() => {
849
+ if (isCurrentPlayer()) {
850
+ client.setKeyboardControls(element.directives.controls)
851
+ }
852
+ })
444
853
  })
445
854
 
446
855
  /**
@@ -467,4 +876,4 @@
467
876
  tick(() => {
468
877
  hooks.callHooks("client-sprite-onUpdate").subscribe()
469
878
  })
470
- </script>
879
+ </script>
@@ -0,0 +1,88 @@
1
+ <Container width={width} height={containerHeight} minWidth={width} minHeight={containerHeight}>
2
+ <Graphics width={width} height={containerHeight} draw={drawBar} />
3
+ @if (hasLabel) {
4
+ <Text text={labelText} x={labelPosition.x} y={labelPosition.y} size={labelSize} color={labelColor} />
5
+ }
6
+ </Container>
7
+
8
+ <script>
9
+ import { computed } from "canvasengine";
10
+ import { resolveDynamicValue } from "./parse-value";
11
+
12
+ const { object, current, max, style, text } = defineProps();
13
+ const sprite = object();
14
+
15
+ const read = (prop, fallback) => prop ? prop() : fallback;
16
+
17
+ const toNumber = (value, fallback = 0) => {
18
+ const resolved = resolveDynamicValue(value, sprite);
19
+ const num = typeof resolved === 'number' ? resolved : parseFloat(resolved);
20
+ return Number.isFinite(num) ? num : fallback;
21
+ };
22
+
23
+ const toColor = (value, fallback) => {
24
+ const resolved = resolveDynamicValue(value, sprite);
25
+ if (typeof resolved === 'number') return resolved;
26
+ if (typeof resolved === 'string' && resolved.startsWith('#')) {
27
+ return parseInt(resolved.slice(1), 16);
28
+ }
29
+ return resolved ?? fallback;
30
+ };
31
+
32
+ const config = computed(() => read(style, {}) ?? {});
33
+ const width = computed(() => toNumber(config().width, 50));
34
+ const height = computed(() => toNumber(config().height, 8));
35
+ const borderRadius = computed(() => toNumber(config().borderRadius, 3));
36
+ const borderWidth = computed(() => toNumber(config().borderWidth, 1));
37
+ const backgroundColor = computed(() => toColor(config().bgColor, 0x16213e));
38
+ const fillColor = computed(() => toColor(config().fillColor, 0x4ade80));
39
+ const borderColor = computed(() => toColor(config().borderColor, 0x4a5568));
40
+ const opacity = computed(() => Math.max(0, Math.min(1, toNumber(config().opacity, 1))));
41
+
42
+ const currentValue = computed(() => toNumber(read(current, 0), 0));
43
+ const maxValue = computed(() => Math.max(0, toNumber(read(max, 0), 0)));
44
+ const percent = computed(() => {
45
+ const max = maxValue();
46
+ if (max <= 0) return 0;
47
+ return Math.max(0, Math.min(1, currentValue() / max));
48
+ });
49
+
50
+ const fillWidth = computed(() => Math.max(0, width() * percent()));
51
+ const labelTemplate = computed(() => read(text, null));
52
+ const labelText = computed(() => {
53
+ const template = labelTemplate();
54
+ if (template == null || template === '') return '';
55
+
56
+ const value = String(template)
57
+ .replace(/\{\$current\}/g, String(currentValue()))
58
+ .replace(/\{\$max\}/g, String(maxValue()))
59
+ .replace(/\{\$percent\}/g, String(Math.round(percent() * 100)));
60
+
61
+ return String(resolveDynamicValue(value, sprite) ?? '');
62
+ });
63
+ const labelSize = computed(() => toNumber(config().fontSize, 10));
64
+ const hasLabel = computed(() => labelText().length > 0);
65
+ const labelOffset = computed(() => hasLabel() ? labelSize() + 2 : 0);
66
+ const containerHeight = computed(() => labelOffset() + height());
67
+ const labelColor = computed(() => toColor(config().textColor, 0xffffff));
68
+ const labelPosition = computed(() => ({
69
+ x: 0,
70
+ y: 0
71
+ }));
72
+
73
+ const drawBar = (g) => {
74
+ g.roundRect(0, labelOffset(), width(), height(), borderRadius());
75
+ g.fill({ color: backgroundColor(), alpha: opacity() });
76
+
77
+ const currentWidth = fillWidth();
78
+ if (currentWidth > 0) {
79
+ g.roundRect(0, labelOffset(), currentWidth, height(), borderRadius());
80
+ g.fill({ color: fillColor(), alpha: opacity() });
81
+ }
82
+
83
+ const strokeWidth = borderWidth();
84
+ if (strokeWidth <= 0) return;
85
+ g.roundRect(0, labelOffset(), width(), height(), borderRadius());
86
+ g.stroke({ color: borderColor(), width: strokeWidth, alpha: opacity() });
87
+ };
88
+ </script>