@rpgjs/client 5.0.0-alpha.9 → 5.0.0-beta.10

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 (347) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/LICENSE +19 -0
  3. package/dist/Game/AnimationManager.d.ts +8 -0
  4. package/dist/Game/AnimationManager.js +35 -0
  5. package/dist/Game/AnimationManager.js.map +1 -0
  6. package/dist/Game/AnimationManager.spec.d.ts +1 -0
  7. package/dist/Game/Event.d.ts +1 -1
  8. package/dist/Game/Event.js +12 -0
  9. package/dist/Game/Event.js.map +1 -0
  10. package/dist/Game/Map.d.ts +31 -2
  11. package/dist/Game/Map.js +138 -0
  12. package/dist/Game/Map.js.map +1 -0
  13. package/dist/Game/Object.d.ts +189 -0
  14. package/dist/Game/Object.js +255 -0
  15. package/dist/Game/Object.js.map +1 -0
  16. package/dist/Game/Player.d.ts +1 -1
  17. package/dist/Game/Player.js +12 -0
  18. package/dist/Game/Player.js.map +1 -0
  19. package/dist/Gui/Gui.d.ts +192 -7
  20. package/dist/Gui/Gui.js +475 -0
  21. package/dist/Gui/Gui.js.map +1 -0
  22. package/dist/Gui/Gui.spec.d.ts +1 -0
  23. package/dist/Gui/NotificationManager.d.ts +23 -0
  24. package/dist/Gui/NotificationManager.js +49 -0
  25. package/dist/Gui/NotificationManager.js.map +1 -0
  26. package/dist/Resource.d.ts +97 -0
  27. package/dist/Resource.js +133 -0
  28. package/dist/Resource.js.map +1 -0
  29. package/dist/RpgClient.d.ts +295 -13
  30. package/dist/RpgClientEngine.d.ts +671 -15
  31. package/dist/RpgClientEngine.js +1442 -0
  32. package/dist/RpgClientEngine.js.map +1 -0
  33. package/dist/Sound.d.ts +199 -0
  34. package/dist/Sound.js +167 -0
  35. package/dist/Sound.js.map +1 -0
  36. package/dist/_virtual/_@oxc-project_runtime@0.130.0/helpers/decorate.js +9 -0
  37. package/dist/_virtual/_@oxc-project_runtime@0.130.0/helpers/decorateMetadata.js +6 -0
  38. package/dist/components/animations/animation.ce.js +23 -0
  39. package/dist/components/animations/animation.ce.js.map +1 -0
  40. package/dist/components/animations/hit.ce.js +64 -0
  41. package/dist/components/animations/hit.ce.js.map +1 -0
  42. package/dist/components/animations/index.d.ts +4 -0
  43. package/dist/components/animations/index.js +11 -0
  44. package/dist/components/animations/index.js.map +1 -0
  45. package/dist/components/character.ce.js +572 -0
  46. package/dist/components/character.ce.js.map +1 -0
  47. package/dist/components/dynamics/bar.ce.js +96 -0
  48. package/dist/components/dynamics/bar.ce.js.map +1 -0
  49. package/dist/components/dynamics/image.ce.js +23 -0
  50. package/dist/components/dynamics/image.ce.js.map +1 -0
  51. package/dist/components/dynamics/parse-value.d.ts +4 -0
  52. package/dist/components/dynamics/parse-value.js +63 -0
  53. package/dist/components/dynamics/parse-value.js.map +1 -0
  54. package/dist/components/dynamics/parse-value.spec.d.ts +1 -0
  55. package/dist/components/dynamics/shape-utils.d.ts +16 -0
  56. package/dist/components/dynamics/shape-utils.js +73 -0
  57. package/dist/components/dynamics/shape-utils.js.map +1 -0
  58. package/dist/components/dynamics/shape-utils.spec.d.ts +1 -0
  59. package/dist/components/dynamics/shape.ce.js +83 -0
  60. package/dist/components/dynamics/shape.ce.js.map +1 -0
  61. package/dist/components/dynamics/text.ce.js +50 -0
  62. package/dist/components/dynamics/text.ce.js.map +1 -0
  63. package/dist/components/gui/box.ce.js +26 -0
  64. package/dist/components/gui/box.ce.js.map +1 -0
  65. package/dist/components/gui/dialogbox/index.ce.js +198 -0
  66. package/dist/components/gui/dialogbox/index.ce.js.map +1 -0
  67. package/dist/components/gui/gameover.ce.js +169 -0
  68. package/dist/components/gui/gameover.ce.js.map +1 -0
  69. package/dist/components/gui/hud/hud.ce.js +83 -0
  70. package/dist/components/gui/hud/hud.ce.js.map +1 -0
  71. package/dist/components/gui/index.d.ts +15 -3
  72. package/dist/components/gui/index.js +14 -0
  73. package/dist/components/gui/menu/equip-menu.ce.js +427 -0
  74. package/dist/components/gui/menu/equip-menu.ce.js.map +1 -0
  75. package/dist/components/gui/menu/exit-menu.ce.js +55 -0
  76. package/dist/components/gui/menu/exit-menu.ce.js.map +1 -0
  77. package/dist/components/gui/menu/items-menu.ce.js +326 -0
  78. package/dist/components/gui/menu/items-menu.ce.js.map +1 -0
  79. package/dist/components/gui/menu/main-menu.ce.js +399 -0
  80. package/dist/components/gui/menu/main-menu.ce.js.map +1 -0
  81. package/dist/components/gui/menu/options-menu.ce.js +49 -0
  82. package/dist/components/gui/menu/options-menu.ce.js.map +1 -0
  83. package/dist/components/gui/menu/skills-menu.ce.js +102 -0
  84. package/dist/components/gui/menu/skills-menu.ce.js.map +1 -0
  85. package/dist/components/gui/mobile/index.d.ts +8 -0
  86. package/dist/components/gui/mobile/index.js +21 -0
  87. package/dist/components/gui/mobile/index.js.map +1 -0
  88. package/dist/components/gui/mobile/mobile.ce.js +79 -0
  89. package/dist/components/gui/mobile/mobile.ce.js.map +1 -0
  90. package/dist/components/gui/notification/notification.ce.js +62 -0
  91. package/dist/components/gui/notification/notification.ce.js.map +1 -0
  92. package/dist/components/gui/save-load.ce.js +211 -0
  93. package/dist/components/gui/save-load.ce.js.map +1 -0
  94. package/dist/components/gui/shop/shop.ce.js +614 -0
  95. package/dist/components/gui/shop/shop.ce.js.map +1 -0
  96. package/dist/components/gui/title-screen.ce.js +164 -0
  97. package/dist/components/gui/title-screen.ce.js.map +1 -0
  98. package/dist/components/index.d.ts +1 -0
  99. package/dist/components/index.js +4 -0
  100. package/dist/components/player-components-utils.d.ts +67 -0
  101. package/dist/components/player-components-utils.js +162 -0
  102. package/dist/components/player-components-utils.js.map +1 -0
  103. package/dist/components/player-components-utils.spec.d.ts +1 -0
  104. package/dist/components/player-components.ce.js +188 -0
  105. package/dist/components/player-components.ce.js.map +1 -0
  106. package/dist/components/prebuilt/hp-bar.ce.js +113 -0
  107. package/dist/components/prebuilt/hp-bar.ce.js.map +1 -0
  108. package/dist/components/prebuilt/index.d.ts +19 -0
  109. package/dist/components/prebuilt/index.js +2 -0
  110. package/dist/components/prebuilt/light-halo.ce.js +70 -0
  111. package/dist/components/prebuilt/light-halo.ce.js.map +1 -0
  112. package/dist/components/scenes/canvas.ce.js +196 -0
  113. package/dist/components/scenes/canvas.ce.js.map +1 -0
  114. package/dist/components/scenes/draw-map.ce.js +79 -0
  115. package/dist/components/scenes/draw-map.ce.js.map +1 -0
  116. package/dist/components/scenes/event-layer.ce.js +29 -0
  117. package/dist/components/scenes/event-layer.ce.js.map +1 -0
  118. package/dist/core/inject.js +18 -0
  119. package/dist/core/inject.js.map +1 -0
  120. package/dist/core/setup.js +16 -0
  121. package/dist/core/setup.js.map +1 -0
  122. package/dist/decorators/spritesheet.d.ts +1 -0
  123. package/dist/decorators/spritesheet.js +11 -0
  124. package/dist/decorators/spritesheet.js.map +1 -0
  125. package/dist/index.d.ts +16 -1
  126. package/dist/index.js +45 -14
  127. package/dist/module.d.ts +43 -4
  128. package/dist/module.js +179 -0
  129. package/dist/module.js.map +1 -0
  130. package/dist/node_modules/.pnpm/@signe_di@3.0.1/node_modules/@signe/di/dist/index.js +167 -0
  131. package/dist/node_modules/.pnpm/@signe_di@3.0.1/node_modules/@signe/di/dist/index.js.map +1 -0
  132. package/dist/node_modules/.pnpm/@signe_reactive@3.0.1/node_modules/@signe/reactive/dist/index.js +239 -0
  133. package/dist/node_modules/.pnpm/@signe_reactive@3.0.1/node_modules/@signe/reactive/dist/index.js.map +1 -0
  134. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/chunk-EUXUH3YW.js +13 -0
  135. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/chunk-EUXUH3YW.js.map +1 -0
  136. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/index.js +696 -0
  137. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/index.js.map +1 -0
  138. package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/client/index.js +44 -0
  139. package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/client/index.js.map +1 -0
  140. package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/index.js +241 -0
  141. package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/index.js.map +1 -0
  142. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-HAC622V3.js +115 -0
  143. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-HAC622V3.js.map +1 -0
  144. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-S74YV6PU.js +401 -0
  145. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-S74YV6PU.js.map +1 -0
  146. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/index.js +2 -0
  147. package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js +3756 -0
  148. package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js.map +1 -0
  149. package/dist/presets/animation.d.ts +31 -0
  150. package/dist/presets/animation.js +39 -0
  151. package/dist/presets/animation.js.map +1 -0
  152. package/dist/presets/faceset.d.ts +30 -0
  153. package/dist/presets/faceset.js +51 -0
  154. package/dist/presets/faceset.js.map +1 -0
  155. package/dist/presets/icon.d.ts +20 -0
  156. package/dist/presets/icon.js +15 -0
  157. package/dist/presets/icon.js.map +1 -0
  158. package/dist/presets/index.d.ts +123 -0
  159. package/dist/presets/index.js +17 -0
  160. package/dist/presets/index.js.map +1 -0
  161. package/dist/presets/lpc.d.ts +89 -0
  162. package/dist/presets/lpc.js +98 -0
  163. package/dist/presets/lpc.js.map +1 -0
  164. package/dist/presets/rmspritesheet.js +42 -0
  165. package/dist/presets/rmspritesheet.js.map +1 -0
  166. package/dist/services/AbstractSocket.d.ts +9 -5
  167. package/dist/services/AbstractSocket.js +11 -0
  168. package/dist/services/AbstractSocket.js.map +1 -0
  169. package/dist/services/keyboardControls.d.ts +15 -0
  170. package/dist/services/keyboardControls.js +23 -0
  171. package/dist/services/keyboardControls.js.map +1 -0
  172. package/dist/services/loadMap.d.ts +6 -0
  173. package/dist/services/loadMap.js +123 -0
  174. package/dist/services/loadMap.js.map +1 -0
  175. package/dist/services/mmorpg.d.ts +21 -9
  176. package/dist/services/mmorpg.js +136 -0
  177. package/dist/services/mmorpg.js.map +1 -0
  178. package/dist/services/save.d.ts +19 -0
  179. package/dist/services/save.js +77 -0
  180. package/dist/services/save.js.map +1 -0
  181. package/dist/services/save.spec.d.ts +1 -0
  182. package/dist/services/standalone.d.ts +67 -7
  183. package/dist/services/standalone.js +168 -0
  184. package/dist/services/standalone.js.map +1 -0
  185. package/dist/utils/getEntityProp.d.ts +39 -0
  186. package/dist/utils/getEntityProp.js +53 -0
  187. package/dist/utils/getEntityProp.js.map +1 -0
  188. package/dist/utils/getEntityProp.spec.d.ts +1 -0
  189. package/dist/utils/readPropValue.d.ts +2 -0
  190. package/dist/utils/readPropValue.js +13 -0
  191. package/dist/utils/readPropValue.js.map +1 -0
  192. package/package.json +14 -11
  193. package/src/Game/AnimationManager.spec.ts +30 -0
  194. package/src/Game/AnimationManager.ts +33 -0
  195. package/src/Game/Event.ts +1 -1
  196. package/src/Game/Map.ts +184 -3
  197. package/src/Game/Object.ts +409 -14
  198. package/src/Game/Player.ts +1 -1
  199. package/src/Gui/Gui.spec.ts +273 -0
  200. package/src/Gui/Gui.ts +566 -23
  201. package/src/Gui/NotificationManager.ts +69 -0
  202. package/src/Resource.ts +149 -0
  203. package/src/RpgClient.ts +309 -14
  204. package/src/RpgClientEngine.ts +1790 -63
  205. package/src/Sound.ts +253 -0
  206. package/src/components/{effects → animations}/animation.ce +3 -6
  207. package/src/components/{effects → animations}/index.ts +1 -1
  208. package/src/components/character.ce +801 -59
  209. package/src/components/dynamics/bar.ce +87 -0
  210. package/src/components/dynamics/image.ce +20 -0
  211. package/src/components/dynamics/parse-value.spec.ts +83 -0
  212. package/src/components/dynamics/parse-value.ts +154 -0
  213. package/src/components/dynamics/shape-utils.spec.ts +46 -0
  214. package/src/components/dynamics/shape-utils.ts +61 -0
  215. package/src/components/dynamics/shape.ce +89 -0
  216. package/src/components/dynamics/text.ce +68 -0
  217. package/src/components/gui/box.ce +17 -0
  218. package/src/components/gui/dialogbox/index.ce +213 -187
  219. package/src/components/gui/gameover.ce +158 -0
  220. package/src/components/gui/hud/hud.ce +61 -0
  221. package/src/components/gui/index.ts +30 -4
  222. package/src/components/gui/menu/equip-menu.ce +410 -0
  223. package/src/components/gui/menu/exit-menu.ce +41 -0
  224. package/src/components/gui/menu/items-menu.ce +317 -0
  225. package/src/components/gui/menu/main-menu.ce +294 -0
  226. package/src/components/gui/menu/options-menu.ce +35 -0
  227. package/src/components/gui/menu/skills-menu.ce +83 -0
  228. package/src/components/gui/mobile/index.ts +24 -0
  229. package/src/components/gui/mobile/mobile.ce +80 -0
  230. package/src/components/gui/notification/notification.ce +51 -0
  231. package/src/components/gui/save-load.ce +208 -0
  232. package/src/components/gui/shop/shop.ce +493 -0
  233. package/src/components/gui/title-screen.ce +163 -0
  234. package/src/components/index.ts +3 -0
  235. package/src/components/player-components-utils.spec.ts +109 -0
  236. package/src/components/player-components-utils.ts +205 -0
  237. package/src/components/player-components.ce +221 -0
  238. package/src/components/prebuilt/hp-bar.ce +255 -0
  239. package/src/components/prebuilt/index.ts +24 -0
  240. package/src/components/prebuilt/light-halo.ce +148 -0
  241. package/src/components/scenes/canvas.ce +185 -21
  242. package/src/components/scenes/draw-map.ce +55 -21
  243. package/src/components/scenes/event-layer.ce +8 -2
  244. package/src/components/scenes/transition.ce +60 -0
  245. package/src/core/setup.ts +2 -2
  246. package/src/decorators/spritesheet.ts +8 -0
  247. package/src/index.ts +17 -2
  248. package/src/module.ts +132 -10
  249. package/src/presets/animation.ts +46 -0
  250. package/src/presets/faceset.ts +60 -0
  251. package/src/presets/icon.ts +17 -0
  252. package/src/presets/index.ts +9 -1
  253. package/src/presets/lpc.ts +108 -0
  254. package/src/services/AbstractSocket.ts +10 -2
  255. package/src/services/keyboardControls.ts +20 -0
  256. package/src/services/loadMap.ts +3 -1
  257. package/src/services/mmorpg.ts +106 -12
  258. package/src/services/save.spec.ts +127 -0
  259. package/src/services/save.ts +103 -0
  260. package/src/services/standalone.ts +110 -18
  261. package/src/utils/getEntityProp.spec.ts +96 -0
  262. package/src/utils/getEntityProp.ts +88 -0
  263. package/src/utils/readPropValue.ts +16 -0
  264. package/vite.config.ts +4 -2
  265. package/dist/Game/EffectManager.d.ts +0 -5
  266. package/dist/components/effects/index.d.ts +0 -4
  267. package/dist/index.js.map +0 -1
  268. package/dist/index10.js +0 -8
  269. package/dist/index10.js.map +0 -1
  270. package/dist/index11.js +0 -10
  271. package/dist/index11.js.map +0 -1
  272. package/dist/index12.js +0 -8
  273. package/dist/index12.js.map +0 -1
  274. package/dist/index13.js +0 -17
  275. package/dist/index13.js.map +0 -1
  276. package/dist/index14.js +0 -107
  277. package/dist/index14.js.map +0 -1
  278. package/dist/index15.js +0 -50
  279. package/dist/index15.js.map +0 -1
  280. package/dist/index16.js +0 -191
  281. package/dist/index16.js.map +0 -1
  282. package/dist/index17.js +0 -9
  283. package/dist/index17.js.map +0 -1
  284. package/dist/index18.js +0 -387
  285. package/dist/index18.js.map +0 -1
  286. package/dist/index19.js +0 -31
  287. package/dist/index19.js.map +0 -1
  288. package/dist/index2.js +0 -181
  289. package/dist/index2.js.map +0 -1
  290. package/dist/index20.js +0 -24
  291. package/dist/index20.js.map +0 -1
  292. package/dist/index21.js +0 -2421
  293. package/dist/index21.js.map +0 -1
  294. package/dist/index22.js +0 -114
  295. package/dist/index22.js.map +0 -1
  296. package/dist/index23.js +0 -109
  297. package/dist/index23.js.map +0 -1
  298. package/dist/index24.js +0 -71
  299. package/dist/index24.js.map +0 -1
  300. package/dist/index25.js +0 -21
  301. package/dist/index25.js.map +0 -1
  302. package/dist/index26.js +0 -41
  303. package/dist/index26.js.map +0 -1
  304. package/dist/index27.js +0 -5
  305. package/dist/index27.js.map +0 -1
  306. package/dist/index28.js +0 -322
  307. package/dist/index28.js.map +0 -1
  308. package/dist/index29.js +0 -27
  309. package/dist/index29.js.map +0 -1
  310. package/dist/index3.js +0 -87
  311. package/dist/index3.js.map +0 -1
  312. package/dist/index30.js +0 -11
  313. package/dist/index30.js.map +0 -1
  314. package/dist/index31.js +0 -11
  315. package/dist/index31.js.map +0 -1
  316. package/dist/index32.js +0 -174
  317. package/dist/index32.js.map +0 -1
  318. package/dist/index33.js +0 -501
  319. package/dist/index33.js.map +0 -1
  320. package/dist/index34.js +0 -12
  321. package/dist/index34.js.map +0 -1
  322. package/dist/index35.js +0 -4403
  323. package/dist/index35.js.map +0 -1
  324. package/dist/index36.js +0 -316
  325. package/dist/index36.js.map +0 -1
  326. package/dist/index37.js +0 -61
  327. package/dist/index37.js.map +0 -1
  328. package/dist/index38.js +0 -20
  329. package/dist/index38.js.map +0 -1
  330. package/dist/index39.js +0 -20
  331. package/dist/index39.js.map +0 -1
  332. package/dist/index4.js +0 -67
  333. package/dist/index4.js.map +0 -1
  334. package/dist/index5.js +0 -16
  335. package/dist/index5.js.map +0 -1
  336. package/dist/index6.js +0 -17
  337. package/dist/index6.js.map +0 -1
  338. package/dist/index7.js +0 -39
  339. package/dist/index7.js.map +0 -1
  340. package/dist/index8.js +0 -108
  341. package/dist/index8.js.map +0 -1
  342. package/dist/index9.js +0 -76
  343. package/dist/index9.js.map +0 -1
  344. package/src/Game/EffectManager.ts +0 -20
  345. package/src/components/gui/dialogbox/itemMenu.ce +0 -23
  346. package/src/components/gui/dialogbox/selection.ce +0 -67
  347. /package/src/components/{effects → animations}/hit.ce +0 -0
@@ -1,16 +1,411 @@
1
- import { RpgCommonPlayer } from "@rpgjs/common";
2
- import { trigger, signal } from "canvasengine";
1
+ import { Hooks, ModulesToken, RpgCommonPlayer } from "@rpgjs/common";
2
+ import { trigger, signal, type Trigger } from "canvasengine";
3
+ import { from, map, of, Subscription, switchMap } from "rxjs";
4
+ import { inject } from "../core/inject";
5
+ import { RpgClientEngine } from "../RpgClientEngine";
6
+ type Frame = { x: number; y: number; ts: number };
7
+
8
+ type AnimationRestoreOptions = {
9
+ restoreAnimationName?: string;
10
+ restoreGraphics?: any[];
11
+ timeoutMs?: number;
12
+ };
13
+
14
+ type FlashType = 'alpha' | 'tint' | 'both';
15
+
16
+ type FlashOptions = {
17
+ type?: FlashType;
18
+ duration?: number;
19
+ cycles?: number;
20
+ alpha?: number;
21
+ tint?: number | string;
22
+ };
23
+
24
+ type FlashTriggerOptions = Omit<FlashOptions, "tint"> & {
25
+ tint: number;
26
+ };
27
+
28
+ type ConfigurableTrigger<T> = Omit<Trigger<T>, "start"> & {
29
+ start(config?: T): Promise<void>;
30
+ };
3
31
 
4
32
  export abstract class RpgClientObject extends RpgCommonPlayer {
5
- abstract type: string;
6
- emitParticleTrigger = trigger()
7
- particleName = signal('')
8
-
9
- flash(color: string, duration: number = 100) {
10
- const lastTint = this.tint()
11
- this.tint.set(color);
12
- setTimeout(() => {
13
- this.tint.set(lastTint)
14
- }, duration)
15
- }
16
- }
33
+ abstract _type: string;
34
+ emitParticleTrigger = trigger();
35
+ particleName = signal("");
36
+ animationCurrentIndex = signal(0);
37
+ animationIsPlaying = signal(false);
38
+ _param = signal({});
39
+ frames: Frame[] = [];
40
+ graphicsSignals = signal<any[]>([]);
41
+ flashTrigger: ConfigurableTrigger<FlashTriggerOptions> = trigger<FlashTriggerOptions>();
42
+ private animationRestoreState?: {
43
+ animationName: string;
44
+ graphics: any[];
45
+ };
46
+
47
+ constructor() {
48
+ super();
49
+ this.hooks.callHooks("client-sprite-onInit", this).subscribe();
50
+
51
+ this._frames.observable.subscribe(({ items }) => {
52
+ if (!this.id) return;
53
+ //if (this.id == this.engine.playerIdSignal()!) return;
54
+ const nextFrames = items.flatMap((item): Frame[] =>
55
+ Array.isArray(item) ? item : [item]
56
+ );
57
+ this.frames = [...this.frames, ...nextFrames];
58
+ });
59
+
60
+ this.graphics.observable
61
+ .pipe(
62
+ map(({ items }) => items),
63
+ switchMap(graphics => {
64
+ if (graphics.length === 0) return of([]);
65
+ return from(Promise.all(graphics.map(graphic => this.engine.getSpriteSheet(graphic))));
66
+ })
67
+ )
68
+ .subscribe((sheets) => {
69
+ this.graphicsSignals.set(sheets);
70
+ });
71
+
72
+ this.engine.tick
73
+ .pipe
74
+ //throttleTime(10)
75
+ ()
76
+ .subscribe(() => {
77
+ const frame = this.frames.shift();
78
+ if (frame) {
79
+ if (typeof frame.x !== "number" || typeof frame.y !== "number") return;
80
+ this.engine.scene.setBodyPosition(
81
+ this.id,
82
+ frame.x,
83
+ frame.y,
84
+ "top-left"
85
+ );
86
+ }
87
+ });
88
+ }
89
+
90
+ /**
91
+ * Access the shared client hook registry.
92
+ *
93
+ * @returns The hook service used to register and trigger client-side hooks.
94
+ */
95
+ get hooks() {
96
+ return inject<Hooks>(ModulesToken);
97
+ }
98
+
99
+ /**
100
+ * Access the current client engine instance.
101
+ *
102
+ * @returns The active {@link RpgClientEngine} instance.
103
+ */
104
+ get engine() {
105
+ return inject(RpgClientEngine);
106
+ }
107
+
108
+ private animationSubscription?: Subscription;
109
+ private animationResetTimeout?: ReturnType<typeof setTimeout>;
110
+ private animationWaitResolve?: () => void;
111
+
112
+ private clearAnimationControls() {
113
+ if (this.animationSubscription) {
114
+ this.animationSubscription.unsubscribe();
115
+ this.animationSubscription = undefined;
116
+ }
117
+ if (this.animationResetTimeout) {
118
+ clearTimeout(this.animationResetTimeout);
119
+ this.animationResetTimeout = undefined;
120
+ }
121
+ }
122
+
123
+ private resolveAnimationWait() {
124
+ const resolve = this.animationWaitResolve;
125
+ this.animationWaitResolve = undefined;
126
+ resolve?.();
127
+ }
128
+
129
+ private finishTemporaryAnimation() {
130
+ const restoreState = this.animationRestoreState;
131
+ this.clearAnimationControls();
132
+ this.animationCurrentIndex.set(0);
133
+ if (restoreState) {
134
+ this.animationName.set(restoreState.animationName);
135
+ this.graphics.set([...restoreState.graphics]);
136
+ }
137
+ this.animationRestoreState = undefined;
138
+ this.animationIsPlaying.set(false);
139
+ this.resolveAnimationWait();
140
+ }
141
+
142
+ /**
143
+ * Trigger a flash animation on this sprite
144
+ *
145
+ * This method triggers a flash effect using CanvasEngine's flash directive.
146
+ * The flash can be configured with various options including type (alpha, tint, or both),
147
+ * duration, cycles, and color.
148
+ *
149
+ * ## Design
150
+ *
151
+ * The flash uses a trigger system that is connected to the flash directive in the
152
+ * character component. This allows for flexible configuration and can be triggered
153
+ * from both server events and client-side code.
154
+ *
155
+ * @param options - Flash configuration options
156
+ * @param options.type - Type of flash effect: 'alpha' (opacity), 'tint' (color), or 'both' (default: 'alpha')
157
+ * @param options.duration - Duration of the flash animation in milliseconds (default: 300)
158
+ * @param options.cycles - Number of flash cycles (flash on/off) (default: 1)
159
+ * @param options.alpha - Alpha value when flashing, from 0 to 1 (default: 0.3)
160
+ * @param options.tint - Tint color when flashing as hex value or color name (default: 0xffffff - white)
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * // Simple flash with default settings (alpha flash)
165
+ * player.flash();
166
+ *
167
+ * // Flash with red tint
168
+ * player.flash({ type: 'tint', tint: 0xff0000 });
169
+ *
170
+ * // Flash with both alpha and tint
171
+ * player.flash({
172
+ * type: 'both',
173
+ * alpha: 0.5,
174
+ * tint: 0xff0000,
175
+ * duration: 200,
176
+ * cycles: 2
177
+ * });
178
+ *
179
+ * // Quick damage flash
180
+ * player.flash({
181
+ * type: 'tint',
182
+ * tint: 0xff0000,
183
+ * duration: 150,
184
+ * cycles: 1
185
+ * });
186
+ * ```
187
+ */
188
+ flash(options?: FlashOptions): void {
189
+ const flashOptions = {
190
+ type: options?.type || 'alpha',
191
+ duration: options?.duration ?? 300,
192
+ cycles: options?.cycles ?? 1,
193
+ alpha: options?.alpha ?? 0.3,
194
+ tint: options?.tint ?? 0xffffff,
195
+ };
196
+
197
+ // Convert color name to hex if needed
198
+ let tintValue = flashOptions.tint;
199
+ if (typeof tintValue === 'string') {
200
+ // Common color name to hex mapping
201
+ const colorMap: Record<string, number> = {
202
+ 'white': 0xffffff,
203
+ 'red': 0xff0000,
204
+ 'green': 0x00ff00,
205
+ 'blue': 0x0000ff,
206
+ 'yellow': 0xffff00,
207
+ 'cyan': 0x00ffff,
208
+ 'magenta': 0xff00ff,
209
+ 'black': 0x000000,
210
+ };
211
+ tintValue = colorMap[tintValue.toLowerCase()] ?? 0xffffff;
212
+ }
213
+
214
+ this.flashTrigger.start({
215
+ ...flashOptions,
216
+ tint: tintValue,
217
+ });
218
+ }
219
+
220
+ /**
221
+ * Reset animation state when animation changes externally
222
+ *
223
+ * This method should be called when the animation changes due to movement
224
+ * or other external factors to ensure the animation system doesn't get stuck
225
+ *
226
+ * @example
227
+ * ```ts
228
+ * // Reset when player starts moving
229
+ * player.resetAnimationState();
230
+ * ```
231
+ */
232
+ resetAnimationState() {
233
+ if (this.animationRestoreState) {
234
+ this.finishTemporaryAnimation();
235
+ return;
236
+ }
237
+ this.animationIsPlaying.set(false);
238
+ this.animationCurrentIndex.set(0);
239
+ this.clearAnimationControls();
240
+ this.resolveAnimationWait();
241
+ }
242
+
243
+ /**
244
+ * Set a custom animation for a specific number of times
245
+ *
246
+ * Plays a custom animation for the specified number of repetitions.
247
+ * The animation system prevents overlapping animations and automatically
248
+ * returns to the previous animation when complete.
249
+ *
250
+ * @param animationName - Name of the animation to play
251
+ * @param nbTimes - Number of times to repeat the animation (default: Infinity for continuous)
252
+ * @param options - Restore and timeout options
253
+ * @returns A promise resolved when a finite animation finishes, is interrupted, or times out
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * // Play attack animation 3 times
258
+ * await player.setAnimation('attack', 3);
259
+ *
260
+ * // Play continuous spell animation
261
+ * player.setAnimation('spell');
262
+ * ```
263
+ */
264
+ setAnimation(animationName: string, nbTimes?: number, options?: AnimationRestoreOptions): Promise<void>;
265
+ /**
266
+ * Set a custom animation with temporary graphic change
267
+ *
268
+ * Plays a custom animation for the specified number of repetitions and temporarily
269
+ * changes the player's graphic (sprite sheet) during the animation. The graphic
270
+ * is automatically reset when the animation finishes.
271
+ *
272
+ * @param animationName - Name of the animation to play
273
+ * @param graphic - The graphic(s) to temporarily use during the animation
274
+ * @param nbTimes - Number of times to repeat the animation (default: Infinity for continuous)
275
+ * @param options - Restore and timeout options
276
+ * @returns A promise resolved when a finite animation finishes, is interrupted, or times out
277
+ *
278
+ * @example
279
+ * ```ts
280
+ * // Play attack animation with temporary graphic change
281
+ * await player.setAnimation('attack', 'hero_attack', 3);
282
+ * ```
283
+ */
284
+ setAnimation(animationName: string, graphic?: string | string[], nbTimes?: number, options?: AnimationRestoreOptions): Promise<void>;
285
+ setAnimation(
286
+ animationName: string,
287
+ graphicOrNbTimes?: string | string[] | number,
288
+ nbTimesOrOptions?: number | AnimationRestoreOptions,
289
+ options?: AnimationRestoreOptions
290
+ ): Promise<void> {
291
+ let graphic: string | string[] | undefined;
292
+ let finalNbTimes: number = Infinity;
293
+ let restoreOptions: AnimationRestoreOptions | undefined = options;
294
+
295
+ // Handle overloads
296
+ if (typeof graphicOrNbTimes === 'number') {
297
+ // setAnimation(animationName, nbTimes)
298
+ finalNbTimes = graphicOrNbTimes;
299
+ restoreOptions = typeof nbTimesOrOptions === 'object' ? nbTimesOrOptions : options;
300
+ } else if (graphicOrNbTimes !== undefined) {
301
+ // setAnimation(animationName, graphic, nbTimes)
302
+ graphic = graphicOrNbTimes;
303
+ if (typeof nbTimesOrOptions === 'number') {
304
+ finalNbTimes = nbTimesOrOptions;
305
+ } else {
306
+ finalNbTimes = Infinity;
307
+ restoreOptions = nbTimesOrOptions ?? options;
308
+ }
309
+ } else {
310
+ // setAnimation(animationName) - nbTimes remains Infinity
311
+ finalNbTimes = Infinity;
312
+ }
313
+
314
+ if (this.animationIsPlaying()) {
315
+ this.finishTemporaryAnimation();
316
+ }
317
+
318
+ const waitPromise =
319
+ finalNbTimes === Infinity
320
+ ? Promise.resolve()
321
+ : new Promise<void>((resolve) => {
322
+ this.animationWaitResolve = resolve;
323
+ });
324
+
325
+ this.animationIsPlaying.set(true);
326
+ const previousAnimationName =
327
+ restoreOptions?.restoreAnimationName ?? this.animationName();
328
+ const previousGraphics = restoreOptions?.restoreGraphics
329
+ ? [...restoreOptions.restoreGraphics]
330
+ : [...this.graphics()];
331
+ this.animationRestoreState = {
332
+ animationName: previousAnimationName,
333
+ graphics: previousGraphics,
334
+ };
335
+ this.animationCurrentIndex.set(0);
336
+
337
+ // Temporarily change graphic if provided
338
+ if (graphic !== undefined) {
339
+ if (Array.isArray(graphic)) {
340
+ this.graphics.set(graphic);
341
+ } else {
342
+ this.graphics.set([graphic]);
343
+ }
344
+ }
345
+
346
+ this.clearAnimationControls();
347
+
348
+ this.animationSubscription =
349
+ this.animationCurrentIndex.observable.subscribe((index) => {
350
+ if (index >= finalNbTimes) {
351
+ this.finishTemporaryAnimation();
352
+ }
353
+ });
354
+
355
+ if (finalNbTimes !== Infinity) {
356
+ this.animationResetTimeout = setTimeout(() => {
357
+ if (this.animationIsPlaying()) {
358
+ this.finishTemporaryAnimation();
359
+ }
360
+ }, restoreOptions?.timeoutMs ?? Math.max(1000, finalNbTimes * 1000));
361
+ }
362
+
363
+ this.animationName.set(animationName);
364
+
365
+ return waitPromise;
366
+ }
367
+
368
+ /**
369
+ * Display a registered component animation effect on this object.
370
+ *
371
+ * @param id - Identifier of the component animation to play.
372
+ * @param params - Parameters forwarded to the animation effect.
373
+ * @returns A promise resolved when the animation component calls `onFinish`.
374
+ */
375
+ showComponentAnimation(id: string, params: any): Promise<void> {
376
+ const engine = inject(RpgClientEngine);
377
+ return engine.getComponentAnimation(id).displayEffect(params, this);
378
+ }
379
+
380
+ /**
381
+ * Display a registered spritesheet animation effect on this object.
382
+ *
383
+ * @param graphic - Identifier of the spritesheet to use.
384
+ * @param animationName - Name of the animation inside the spritesheet.
385
+ * @returns A promise resolved when the animation component calls `onFinish`.
386
+ */
387
+ showAnimation(graphic: string, animationName: string = 'default'): Promise<void> {
388
+ return this.showComponentAnimation('animation', {
389
+ graphic,
390
+ animationName,
391
+ });
392
+ }
393
+
394
+ /**
395
+ * Check whether this client object represents an event.
396
+ *
397
+ * @returns `true` if the object type is `event`, otherwise `false`.
398
+ */
399
+ isEvent(): boolean {
400
+ return this._type === 'event';
401
+ }
402
+
403
+ /**
404
+ * Check whether this client object represents a player.
405
+ *
406
+ * @returns `true` if the object type is `player`, otherwise `false`.
407
+ */
408
+ isPlayer(): boolean {
409
+ return this._type === 'player';
410
+ }
411
+ }
@@ -1,5 +1,5 @@
1
1
  import { RpgClientObject } from "./Object";
2
2
 
3
3
  export class RpgClientPlayer extends RpgClientObject {
4
- type = 'player'
4
+ _type = 'player'
5
5
  }
@@ -0,0 +1,273 @@
1
+ import { describe, expect, test, vi } from "vitest";
2
+ import { Context, injector } from "@signe/di";
3
+ import { signal } from "canvasengine";
4
+ import { PrebuiltGui } from "@rpgjs/common";
5
+ import { WebSocketToken } from "../services/AbstractSocket";
6
+
7
+ vi.mock("../components/gui", () => {
8
+ const component = () => null;
9
+ return {
10
+ DialogboxComponent: component,
11
+ ShopComponent: component,
12
+ SaveLoadComponent: component,
13
+ MainMenuComponent: component,
14
+ NotificationComponent: component,
15
+ TitleScreenComponent: component,
16
+ GameoverComponent: component,
17
+ };
18
+ });
19
+
20
+ const createGui = async () => {
21
+ const { RpgGui } = await import("./Gui");
22
+ const context = new Context();
23
+ const socket = {
24
+ on: vi.fn(),
25
+ emit: vi.fn(),
26
+ };
27
+ await injector(context, [
28
+ {
29
+ provide: WebSocketToken,
30
+ useValue: socket,
31
+ },
32
+ ]);
33
+ return {
34
+ gui: new RpgGui(context),
35
+ socket,
36
+ };
37
+ };
38
+
39
+ const CanvasGui = () => null;
40
+ const VueInventory = {
41
+ name: "inventory",
42
+ render() {
43
+ return null;
44
+ },
45
+ };
46
+ const VueDialog = {
47
+ name: PrebuiltGui.Dialog,
48
+ render() {
49
+ return null;
50
+ },
51
+ };
52
+ const VueMainMenu = {
53
+ name: PrebuiltGui.MainMenu,
54
+ render() {
55
+ return null;
56
+ },
57
+ };
58
+ const VueTooltip = {
59
+ name: "tooltip",
60
+ rpgAttachToSprite: true,
61
+ render() {
62
+ return null;
63
+ },
64
+ };
65
+
66
+ describe("RpgGui Vue integration", () => {
67
+ test("separates CanvasEngine and Vue GUI registries", async () => {
68
+ const { gui } = await createGui();
69
+
70
+ gui.add({
71
+ id: "canvas-tooltip",
72
+ component: CanvasGui,
73
+ attachToSprite: true,
74
+ });
75
+ gui.add({
76
+ id: "inventory",
77
+ component: VueInventory,
78
+ });
79
+ gui.add(VueTooltip);
80
+
81
+ expect(gui.get("canvas-tooltip")?.component).toBe(CanvasGui);
82
+ expect(gui.get("inventory")?.component).toBe(VueInventory);
83
+ expect(gui.get("tooltip")?.component).toBe(VueTooltip);
84
+ expect(gui.getAttachedGuis().map(item => item.name)).toEqual(["canvas-tooltip"]);
85
+ expect(gui.getAttachedVueGuis().map(item => item.name)).toEqual(["tooltip"]);
86
+ });
87
+
88
+ test("synchronizes Vue GUI display and hide states through the Vue bridge", async () => {
89
+ const { gui } = await createGui();
90
+ const bridge = {
91
+ updateGuiState: vi.fn(),
92
+ initializeGuiStates: vi.fn(),
93
+ };
94
+
95
+ gui.add({
96
+ id: "inventory",
97
+ component: VueInventory,
98
+ });
99
+ gui._setVueGuiInstance(bridge);
100
+
101
+ expect(bridge.initializeGuiStates).toHaveBeenCalledWith([
102
+ expect.objectContaining({
103
+ name: "inventory",
104
+ display: false,
105
+ data: {},
106
+ attachToSprite: false,
107
+ }),
108
+ ]);
109
+
110
+ gui.display("inventory", { gold: 12 });
111
+ expect(bridge.updateGuiState).toHaveBeenLastCalledWith(
112
+ expect.objectContaining({
113
+ name: "inventory",
114
+ display: true,
115
+ data: { gold: 12 },
116
+ attachToSprite: false,
117
+ }),
118
+ );
119
+
120
+ gui.hide("inventory");
121
+ expect(bridge.updateGuiState).toHaveBeenLastCalledWith(
122
+ expect.objectContaining({
123
+ name: "inventory",
124
+ display: false,
125
+ }),
126
+ );
127
+ });
128
+
129
+ test("waits for Vue GUI dependencies before display", async () => {
130
+ const { gui } = await createGui();
131
+ const bridge = {
132
+ updateGuiState: vi.fn(),
133
+ initializeGuiStates: vi.fn(),
134
+ };
135
+ const dependency = signal<any>(undefined);
136
+
137
+ gui.add({
138
+ id: "inventory",
139
+ component: VueInventory,
140
+ dependencies: () => [dependency],
141
+ });
142
+ gui._setVueGuiInstance(bridge);
143
+ gui.display("inventory", { items: ["potion"] });
144
+
145
+ expect(gui.isDisplaying("inventory")).toBe(false);
146
+ expect(bridge.updateGuiState).not.toHaveBeenCalledWith(
147
+ expect.objectContaining({
148
+ display: true,
149
+ }),
150
+ );
151
+
152
+ dependency.set({ id: "player" });
153
+
154
+ expect(gui.isDisplaying("inventory")).toBe(true);
155
+ expect(bridge.updateGuiState).toHaveBeenLastCalledWith(
156
+ expect.objectContaining({
157
+ name: "inventory",
158
+ display: true,
159
+ data: { items: ["potion"] },
160
+ }),
161
+ );
162
+ });
163
+
164
+ test("allows Vue GUI entries to replace prebuilt CanvasEngine GUIs", async () => {
165
+ const { gui } = await createGui();
166
+ const bridge = {
167
+ updateGuiState: vi.fn(),
168
+ initializeGuiStates: vi.fn(),
169
+ };
170
+
171
+ gui._setVueGuiInstance(bridge);
172
+ gui.add({
173
+ id: PrebuiltGui.Dialog,
174
+ component: VueDialog,
175
+ });
176
+
177
+ expect(gui.get(PrebuiltGui.Dialog)?.component).toBe(VueDialog);
178
+ expect(gui.getAll()[PrebuiltGui.Dialog].component).toBe(VueDialog);
179
+ expect((gui as any).gui()[PrebuiltGui.Dialog]).toBeUndefined();
180
+ expect(gui.getVueGuis().filter(item => item.name === PrebuiltGui.Dialog)).toHaveLength(1);
181
+
182
+ gui.display(PrebuiltGui.Dialog, { text: "Hello" });
183
+ expect(bridge.updateGuiState).toHaveBeenLastCalledWith(
184
+ expect.objectContaining({
185
+ name: PrebuiltGui.Dialog,
186
+ display: true,
187
+ data: { text: "Hello" },
188
+ }),
189
+ );
190
+
191
+ gui.hide(PrebuiltGui.Dialog);
192
+ expect(bridge.updateGuiState).toHaveBeenLastCalledWith(
193
+ expect.objectContaining({
194
+ name: PrebuiltGui.Dialog,
195
+ display: false,
196
+ }),
197
+ );
198
+ });
199
+
200
+ test("allows CanvasEngine GUI entries to replace Vue GUI entries with the same id", async () => {
201
+ const { gui } = await createGui();
202
+ const bridge = {
203
+ updateGuiState: vi.fn(),
204
+ initializeGuiStates: vi.fn(),
205
+ };
206
+
207
+ gui._setVueGuiInstance(bridge);
208
+ gui.add({
209
+ id: PrebuiltGui.Dialog,
210
+ component: VueDialog,
211
+ });
212
+ gui.add({
213
+ id: PrebuiltGui.Dialog,
214
+ component: CanvasGui,
215
+ });
216
+
217
+ expect(gui.get(PrebuiltGui.Dialog)?.component).toBe(CanvasGui);
218
+ expect(gui.getVueGuis().some(item => item.name === PrebuiltGui.Dialog)).toBe(false);
219
+ expect((gui as any).gui()[PrebuiltGui.Dialog].component).toBe(CanvasGui);
220
+ expect(bridge.initializeGuiStates).toHaveBeenLastCalledWith([]);
221
+ });
222
+
223
+ test("keeps main menu optimistic reducers when a Vue GUI replaces the prebuilt component", async () => {
224
+ const { gui, socket } = await createGui();
225
+ const bridge = {
226
+ updateGuiState: vi.fn(),
227
+ initializeGuiStates: vi.fn(),
228
+ };
229
+
230
+ gui.add({
231
+ id: PrebuiltGui.MainMenu,
232
+ component: VueMainMenu,
233
+ });
234
+ gui._setVueGuiInstance(bridge);
235
+ gui.display(PrebuiltGui.MainMenu, {
236
+ items: [
237
+ {
238
+ id: "potion",
239
+ quantity: 2,
240
+ },
241
+ ],
242
+ });
243
+
244
+ gui.guiInteraction(PrebuiltGui.MainMenu, "useItem", { id: "potion" });
245
+
246
+ expect(gui.get(PrebuiltGui.MainMenu)?.data().items).toEqual([
247
+ {
248
+ id: "potion",
249
+ quantity: 1,
250
+ },
251
+ ]);
252
+ expect(bridge.updateGuiState).toHaveBeenLastCalledWith(
253
+ expect.objectContaining({
254
+ name: PrebuiltGui.MainMenu,
255
+ data: {
256
+ items: [
257
+ {
258
+ id: "potion",
259
+ quantity: 1,
260
+ },
261
+ ],
262
+ },
263
+ }),
264
+ );
265
+ expect(socket.emit).toHaveBeenCalledWith(
266
+ "gui.interaction",
267
+ expect.objectContaining({
268
+ guiId: PrebuiltGui.MainMenu,
269
+ name: "useItem",
270
+ }),
271
+ );
272
+ });
273
+ });