@rpgjs/client 5.0.0-beta.2 → 5.0.0-beta.22

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 (325) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/dist/Game/AnimationManager.d.ts +2 -1
  3. package/dist/Game/AnimationManager.js +21 -9
  4. package/dist/Game/AnimationManager.js.map +1 -1
  5. package/dist/Game/AnimationManager.spec.d.ts +1 -0
  6. package/dist/Game/ClientVisuals.d.ts +61 -0
  7. package/dist/Game/ClientVisuals.js +96 -0
  8. package/dist/Game/ClientVisuals.js.map +1 -0
  9. package/dist/Game/ClientVisuals.spec.d.ts +1 -0
  10. package/dist/Game/Event.js.map +1 -1
  11. package/dist/Game/EventComponentResolver.d.ts +16 -0
  12. package/dist/Game/EventComponentResolver.js +52 -0
  13. package/dist/Game/EventComponentResolver.js.map +1 -0
  14. package/dist/Game/EventComponentResolver.spec.d.ts +1 -0
  15. package/dist/Game/Map.d.ts +9 -1
  16. package/dist/Game/Map.js +83 -5
  17. package/dist/Game/Map.js.map +1 -1
  18. package/dist/Game/Object.d.ts +49 -15
  19. package/dist/Game/Object.js +101 -42
  20. package/dist/Game/Object.js.map +1 -1
  21. package/dist/Game/Object.spec.d.ts +1 -0
  22. package/dist/Game/Player.js.map +1 -1
  23. package/dist/Game/ProjectileManager.d.ts +98 -0
  24. package/dist/Game/ProjectileManager.js +196 -0
  25. package/dist/Game/ProjectileManager.js.map +1 -0
  26. package/dist/Game/ProjectileManager.spec.d.ts +1 -0
  27. package/dist/Gui/Gui.d.ts +20 -6
  28. package/dist/Gui/Gui.js +132 -57
  29. package/dist/Gui/Gui.js.map +1 -1
  30. package/dist/Gui/Gui.spec.d.ts +1 -0
  31. package/dist/Gui/NotificationManager.js.map +1 -1
  32. package/dist/Resource.js +1 -1
  33. package/dist/Resource.js.map +1 -1
  34. package/dist/RpgClient.d.ts +194 -15
  35. package/dist/RpgClientEngine.d.ts +191 -19
  36. package/dist/RpgClientEngine.js +693 -74
  37. package/dist/RpgClientEngine.js.map +1 -1
  38. package/dist/Sound.js.map +1 -1
  39. package/dist/_virtual/{_@oxc-project_runtime@0.122.0/helpers → _@oxc-project_runtime@0.133.0/helpers/esm}/decorate.js +1 -1
  40. package/dist/_virtual/{_@oxc-project_runtime@0.122.0/helpers → _@oxc-project_runtime@0.133.0/helpers/esm}/decorateMetadata.js +1 -1
  41. package/dist/_virtual/_rolldown/runtime.js +13 -0
  42. package/dist/components/animations/animation.ce.js +6 -5
  43. package/dist/components/animations/animation.ce.js.map +1 -1
  44. package/dist/components/animations/fx.ce.js +60 -0
  45. package/dist/components/animations/fx.ce.js.map +1 -0
  46. package/dist/components/animations/hit.ce.js +24 -28
  47. package/dist/components/animations/hit.ce.js.map +1 -1
  48. package/dist/components/animations/index.d.ts +1 -0
  49. package/dist/components/animations/index.js +6 -4
  50. package/dist/components/animations/index.js.map +1 -1
  51. package/dist/components/character.ce.js +748 -249
  52. package/dist/components/character.ce.js.map +1 -1
  53. package/dist/components/dynamics/bar.ce.js +99 -0
  54. package/dist/components/dynamics/bar.ce.js.map +1 -0
  55. package/dist/components/dynamics/image.ce.js +26 -0
  56. package/dist/components/dynamics/image.ce.js.map +1 -0
  57. package/dist/components/dynamics/parse-value.d.ts +3 -0
  58. package/dist/components/dynamics/parse-value.js +54 -35
  59. package/dist/components/dynamics/parse-value.js.map +1 -1
  60. package/dist/components/dynamics/parse-value.spec.d.ts +1 -0
  61. package/dist/components/dynamics/shape-utils.d.ts +16 -0
  62. package/dist/components/dynamics/shape-utils.js +73 -0
  63. package/dist/components/dynamics/shape-utils.js.map +1 -0
  64. package/dist/components/dynamics/shape-utils.spec.d.ts +1 -0
  65. package/dist/components/dynamics/shape.ce.js +86 -0
  66. package/dist/components/dynamics/shape.ce.js.map +1 -0
  67. package/dist/components/dynamics/text.ce.js +36 -56
  68. package/dist/components/dynamics/text.ce.js.map +1 -1
  69. package/dist/components/gui/box.ce.js +9 -9
  70. package/dist/components/gui/box.ce.js.map +1 -1
  71. package/dist/components/gui/dialogbox/index.ce.js +75 -65
  72. package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
  73. package/dist/components/gui/gameover.ce.js +47 -68
  74. package/dist/components/gui/gameover.ce.js.map +1 -1
  75. package/dist/components/gui/hud/hud.ce.js +23 -31
  76. package/dist/components/gui/hud/hud.ce.js.map +1 -1
  77. package/dist/components/gui/menu/equip-menu.ce.js +121 -172
  78. package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
  79. package/dist/components/gui/menu/exit-menu.ce.js +15 -10
  80. package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
  81. package/dist/components/gui/menu/items-menu.ce.js +62 -77
  82. package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
  83. package/dist/components/gui/menu/main-menu.ce.js +89 -104
  84. package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
  85. package/dist/components/gui/menu/options-menu.ce.js +12 -8
  86. package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
  87. package/dist/components/gui/menu/skills-menu.ce.js +17 -19
  88. package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
  89. package/dist/components/gui/mobile/index.d.ts +51 -2
  90. package/dist/components/gui/mobile/index.js +14 -6
  91. package/dist/components/gui/mobile/index.js.map +1 -1
  92. package/dist/components/gui/mobile/index.spec.d.ts +1 -0
  93. package/dist/components/gui/mobile/mobile.ce.js +309 -59
  94. package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
  95. package/dist/components/gui/notification/notification.ce.js +28 -26
  96. package/dist/components/gui/notification/notification.ce.js.map +1 -1
  97. package/dist/components/gui/save-load.ce.js +81 -256
  98. package/dist/components/gui/save-load.ce.js.map +1 -1
  99. package/dist/components/gui/shop/shop.ce.js +107 -142
  100. package/dist/components/gui/shop/shop.ce.js.map +1 -1
  101. package/dist/components/gui/title-screen.ce.js +50 -73
  102. package/dist/components/gui/title-screen.ce.js.map +1 -1
  103. package/dist/components/index.d.ts +2 -1
  104. package/dist/components/index.js +1 -0
  105. package/dist/components/interaction-components.ce.js +22 -0
  106. package/dist/components/interaction-components.ce.js.map +1 -0
  107. package/dist/components/player-components-utils.d.ts +67 -0
  108. package/dist/components/player-components-utils.js +162 -0
  109. package/dist/components/player-components-utils.js.map +1 -0
  110. package/dist/components/player-components-utils.spec.d.ts +1 -0
  111. package/dist/components/player-components.ce.js +191 -0
  112. package/dist/components/player-components.ce.js.map +1 -0
  113. package/dist/components/prebuilt/hp-bar.ce.js +45 -45
  114. package/dist/components/prebuilt/hp-bar.ce.js.map +1 -1
  115. package/dist/components/prebuilt/light-halo.ce.js +39 -60
  116. package/dist/components/prebuilt/light-halo.ce.js.map +1 -1
  117. package/dist/components/scenes/canvas.ce.js +205 -27
  118. package/dist/components/scenes/canvas.ce.js.map +1 -1
  119. package/dist/components/scenes/draw-map.ce.js +39 -39
  120. package/dist/components/scenes/draw-map.ce.js.map +1 -1
  121. package/dist/components/scenes/event-layer.ce.js +51 -10
  122. package/dist/components/scenes/event-layer.ce.js.map +1 -1
  123. package/dist/core/inject.js +1 -1
  124. package/dist/core/inject.js.map +1 -1
  125. package/dist/core/setup.js +1 -1
  126. package/dist/core/setup.js.map +1 -1
  127. package/dist/decorators/spritesheet.d.ts +1 -0
  128. package/dist/decorators/spritesheet.js +11 -0
  129. package/dist/decorators/spritesheet.js.map +1 -0
  130. package/dist/i18n.d.ts +55 -0
  131. package/dist/i18n.js +60 -0
  132. package/dist/i18n.js.map +1 -0
  133. package/dist/i18n.spec.d.ts +1 -0
  134. package/dist/index.d.ts +8 -0
  135. package/dist/index.js +29 -21
  136. package/dist/module.js +45 -4
  137. package/dist/module.js.map +1 -1
  138. package/dist/node_modules/.pnpm/{@signe_di@2.9.0 → @signe_di@3.1.0}/node_modules/@signe/di/dist/index.js +7 -117
  139. package/dist/node_modules/.pnpm/@signe_di@3.1.0/node_modules/@signe/di/dist/index.js.map +1 -0
  140. package/dist/node_modules/.pnpm/@signe_reactive@3.1.0/node_modules/@signe/reactive/dist/index.js +239 -0
  141. package/dist/node_modules/.pnpm/@signe_reactive@3.1.0/node_modules/@signe/reactive/dist/index.js.map +1 -0
  142. package/dist/node_modules/.pnpm/@signe_room@3.1.0_react@19.2.7/node_modules/@signe/room/dist/chunk-EUXUH3YW.js +13 -0
  143. package/dist/node_modules/.pnpm/@signe_room@3.1.0_react@19.2.7/node_modules/@signe/room/dist/chunk-EUXUH3YW.js.map +1 -0
  144. package/dist/node_modules/.pnpm/@signe_room@3.1.0_react@19.2.7/node_modules/@signe/room/dist/index.js +698 -0
  145. package/dist/node_modules/.pnpm/@signe_room@3.1.0_react@19.2.7/node_modules/@signe/room/dist/index.js.map +1 -0
  146. package/dist/node_modules/.pnpm/@signe_sync@3.1.0_react@19.2.7/node_modules/@signe/sync/dist/client/index.js +43 -0
  147. package/dist/node_modules/.pnpm/@signe_sync@3.1.0_react@19.2.7/node_modules/@signe/sync/dist/client/index.js.map +1 -0
  148. package/dist/node_modules/.pnpm/{@signe_sync@2.9.0 → @signe_sync@3.1.0_react@19.2.7}/node_modules/@signe/sync/dist/index.js +57 -141
  149. package/dist/node_modules/.pnpm/@signe_sync@3.1.0_react@19.2.7/node_modules/@signe/sync/dist/index.js.map +1 -0
  150. package/dist/node_modules/.pnpm/{partysocket@1.1.3/node_modules/partysocket/dist/chunk-HAC622V3.js → partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/index.js} +30 -17
  151. package/dist/node_modules/.pnpm/partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/index.js.map +1 -0
  152. package/dist/node_modules/.pnpm/{partysocket@1.1.3/node_modules/partysocket/dist/chunk-S74YV6PU.js → partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/ws.js} +65 -34
  153. package/dist/node_modules/.pnpm/partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/ws.js.map +1 -0
  154. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/index.js +13 -0
  155. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/index.js.map +1 -0
  156. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.js +106 -0
  157. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.js.map +1 -0
  158. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.js +13 -0
  159. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.js.map +1 -0
  160. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js +121 -0
  161. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js.map +1 -0
  162. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js +10 -0
  163. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js.map +1 -0
  164. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js +110 -0
  165. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js.map +1 -0
  166. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js +99 -0
  167. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js.map +1 -0
  168. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/locales/en.js +82 -0
  169. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/locales/en.js.map +1 -0
  170. package/dist/node_modules/.pnpm/{zod@3.24.2/node_modules/zod/lib/index.js → zod@3.25.76/node_modules/zod/v3/types.js} +74 -611
  171. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js.map +1 -0
  172. package/dist/presets/animation.js.map +1 -1
  173. package/dist/presets/faceset.js.map +1 -1
  174. package/dist/presets/icon.js.map +1 -1
  175. package/dist/presets/index.js.map +1 -1
  176. package/dist/presets/lpc.js.map +1 -1
  177. package/dist/presets/rmspritesheet.js.map +1 -1
  178. package/dist/services/AbstractSocket.d.ts +2 -0
  179. package/dist/services/AbstractSocket.js.map +1 -1
  180. package/dist/services/actionInput.d.ts +14 -0
  181. package/dist/services/actionInput.js +59 -0
  182. package/dist/services/actionInput.js.map +1 -0
  183. package/dist/services/actionInput.spec.d.ts +1 -0
  184. package/dist/services/cameraFollow.d.ts +51 -0
  185. package/dist/services/cameraFollow.js +134 -0
  186. package/dist/services/cameraFollow.js.map +1 -0
  187. package/dist/services/cameraFollow.spec.d.ts +1 -0
  188. package/dist/services/interactions.d.ts +159 -0
  189. package/dist/services/interactions.js +460 -0
  190. package/dist/services/interactions.js.map +1 -0
  191. package/dist/services/interactions.spec.d.ts +1 -0
  192. package/dist/services/keyboardControls.d.ts +1 -0
  193. package/dist/services/keyboardControls.js +1 -0
  194. package/dist/services/keyboardControls.js.map +1 -1
  195. package/dist/services/loadMap.d.ts +9 -0
  196. package/dist/services/loadMap.js +1 -1
  197. package/dist/services/loadMap.js.map +1 -1
  198. package/dist/services/mmorpg-connection.d.ts +5 -0
  199. package/dist/services/mmorpg-connection.js +50 -0
  200. package/dist/services/mmorpg-connection.js.map +1 -0
  201. package/dist/services/mmorpg-connection.spec.d.ts +1 -0
  202. package/dist/services/mmorpg.d.ts +11 -4
  203. package/dist/services/mmorpg.js +57 -33
  204. package/dist/services/mmorpg.js.map +1 -1
  205. package/dist/services/pointerContext.d.ts +11 -0
  206. package/dist/services/pointerContext.js +48 -0
  207. package/dist/services/pointerContext.js.map +1 -0
  208. package/dist/services/pointerContext.spec.d.ts +1 -0
  209. package/dist/services/save.js.map +1 -1
  210. package/dist/services/save.spec.d.ts +1 -0
  211. package/dist/services/standalone-message.d.ts +1 -0
  212. package/dist/services/standalone-message.js +9 -0
  213. package/dist/services/standalone-message.js.map +1 -0
  214. package/dist/services/standalone.d.ts +4 -1
  215. package/dist/services/standalone.js +36 -16
  216. package/dist/services/standalone.js.map +1 -1
  217. package/dist/services/standalone.spec.d.ts +1 -0
  218. package/dist/utils/getEntityProp.js +4 -3
  219. package/dist/utils/getEntityProp.js.map +1 -1
  220. package/dist/utils/getEntityProp.spec.d.ts +1 -0
  221. package/dist/utils/mapId.d.ts +1 -0
  222. package/dist/utils/mapId.js +6 -0
  223. package/dist/utils/mapId.js.map +1 -0
  224. package/dist/utils/readPropValue.d.ts +2 -0
  225. package/dist/utils/readPropValue.js +13 -0
  226. package/dist/utils/readPropValue.js.map +1 -0
  227. package/package.json +13 -13
  228. package/src/Game/AnimationManager.spec.ts +30 -0
  229. package/src/Game/AnimationManager.ts +26 -10
  230. package/src/Game/ClientVisuals.spec.ts +56 -0
  231. package/src/Game/ClientVisuals.ts +184 -0
  232. package/src/Game/EventComponentResolver.spec.ts +84 -0
  233. package/src/Game/EventComponentResolver.ts +74 -0
  234. package/src/Game/Map.ts +134 -2
  235. package/src/Game/Object.spec.ts +59 -0
  236. package/src/Game/Object.ts +181 -77
  237. package/src/Game/ProjectileManager.spec.ts +449 -0
  238. package/src/Game/ProjectileManager.ts +346 -0
  239. package/src/Gui/Gui.spec.ts +359 -0
  240. package/src/Gui/Gui.ts +178 -67
  241. package/src/Resource.ts +1 -2
  242. package/src/RpgClient.ts +220 -17
  243. package/src/RpgClientEngine.ts +995 -133
  244. package/src/components/animations/fx.ce +101 -0
  245. package/src/components/animations/index.ts +4 -2
  246. package/src/components/character.ce +839 -45
  247. package/src/components/dynamics/bar.ce +88 -0
  248. package/src/components/dynamics/image.ce +21 -0
  249. package/src/components/dynamics/parse-value.spec.ts +83 -0
  250. package/src/components/dynamics/parse-value.ts +111 -37
  251. package/src/components/dynamics/shape-utils.spec.ts +46 -0
  252. package/src/components/dynamics/shape-utils.ts +61 -0
  253. package/src/components/dynamics/shape.ce +90 -0
  254. package/src/components/dynamics/text.ce +35 -149
  255. package/src/components/gui/dialogbox/index.ce +52 -21
  256. package/src/components/gui/gameover.ce +6 -4
  257. package/src/components/gui/menu/equip-menu.ce +11 -9
  258. package/src/components/gui/menu/exit-menu.ce +6 -4
  259. package/src/components/gui/menu/items-menu.ce +11 -9
  260. package/src/components/gui/menu/main-menu.ce +14 -12
  261. package/src/components/gui/menu/options-menu.ce +4 -3
  262. package/src/components/gui/menu/skills-menu.ce +2 -1
  263. package/src/components/gui/mobile/index.spec.ts +94 -0
  264. package/src/components/gui/mobile/index.ts +74 -6
  265. package/src/components/gui/mobile/mobile.ce +347 -65
  266. package/src/components/gui/notification/notification.ce +7 -1
  267. package/src/components/gui/save-load.ce +13 -11
  268. package/src/components/gui/shop/shop.ce +20 -18
  269. package/src/components/gui/title-screen.ce +6 -4
  270. package/src/components/index.ts +2 -1
  271. package/src/components/interaction-components.ce +23 -0
  272. package/src/components/player-components-utils.spec.ts +109 -0
  273. package/src/components/player-components-utils.ts +205 -0
  274. package/src/components/player-components.ce +222 -0
  275. package/src/components/prebuilt/hp-bar.ce +4 -3
  276. package/src/components/prebuilt/light-halo.ce +2 -2
  277. package/src/components/scenes/canvas.ce +219 -15
  278. package/src/components/scenes/draw-map.ce +34 -22
  279. package/src/components/scenes/event-layer.ce +55 -4
  280. package/src/core/setup.ts +2 -2
  281. package/src/decorators/spritesheet.ts +8 -0
  282. package/src/i18n.spec.ts +39 -0
  283. package/src/i18n.ts +59 -0
  284. package/src/index.ts +19 -0
  285. package/src/module.ts +61 -11
  286. package/src/services/AbstractSocket.ts +3 -0
  287. package/src/services/actionInput.spec.ts +155 -0
  288. package/src/services/actionInput.ts +120 -0
  289. package/src/services/cameraFollow.spec.ts +220 -0
  290. package/src/services/cameraFollow.ts +222 -0
  291. package/src/services/interactions.spec.ts +175 -0
  292. package/src/services/interactions.ts +722 -0
  293. package/src/services/keyboardControls.ts +2 -1
  294. package/src/services/loadMap.ts +5 -1
  295. package/src/services/mmorpg-connection.spec.ts +99 -0
  296. package/src/services/mmorpg-connection.ts +69 -0
  297. package/src/services/mmorpg.ts +70 -36
  298. package/src/services/pointerContext.spec.ts +36 -0
  299. package/src/services/pointerContext.ts +84 -0
  300. package/src/services/save.spec.ts +127 -0
  301. package/src/services/standalone-message.ts +7 -0
  302. package/src/services/standalone.spec.ts +54 -0
  303. package/src/services/standalone.ts +44 -12
  304. package/src/utils/getEntityProp.spec.ts +96 -0
  305. package/src/utils/getEntityProp.ts +4 -3
  306. package/src/utils/mapId.ts +2 -0
  307. package/src/utils/readPropValue.ts +16 -0
  308. package/dist/node_modules/.pnpm/@signe_di@2.9.0/node_modules/@signe/di/dist/index.js.map +0 -1
  309. package/dist/node_modules/.pnpm/@signe_reactive@2.8.3/node_modules/@signe/reactive/dist/index.js +0 -457
  310. package/dist/node_modules/.pnpm/@signe_reactive@2.8.3/node_modules/@signe/reactive/dist/index.js.map +0 -1
  311. package/dist/node_modules/.pnpm/@signe_reactive@2.9.0/node_modules/@signe/reactive/dist/index.js +0 -463
  312. package/dist/node_modules/.pnpm/@signe_reactive@2.9.0/node_modules/@signe/reactive/dist/index.js.map +0 -1
  313. package/dist/node_modules/.pnpm/@signe_room@2.9.0/node_modules/@signe/room/dist/index.js +0 -2191
  314. package/dist/node_modules/.pnpm/@signe_room@2.9.0/node_modules/@signe/room/dist/index.js.map +0 -1
  315. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/chunk-7QVYU63E.js +0 -10
  316. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/chunk-7QVYU63E.js.map +0 -1
  317. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/client/index.js +0 -91
  318. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/client/index.js.map +0 -1
  319. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/index.js.map +0 -1
  320. package/dist/node_modules/.pnpm/dset@3.1.4/node_modules/dset/dist/index.js +0 -14
  321. package/dist/node_modules/.pnpm/dset@3.1.4/node_modules/dset/dist/index.js.map +0 -1
  322. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-HAC622V3.js.map +0 -1
  323. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-S74YV6PU.js.map +0 -1
  324. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/index.js +0 -2
  325. package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js.map +0 -1
@@ -1,45 +1,127 @@
1
1
  import { inject } from "./core/inject.js";
2
2
  import { WebSocketToken } from "./services/AbstractSocket.js";
3
+ import { normalizeActionInput } from "./services/actionInput.js";
4
+ import { getCanMoveValue } from "./utils/readPropValue.js";
3
5
  import { SaveClientService } from "./services/save.js";
4
6
  import { RpgGui } from "./Gui/Gui.js";
5
- import component from "./components/scenes/canvas.ce.js";
7
+ import __ce_component from "./components/scenes/canvas.ce.js";
8
+ import __ce_component$1 from "./components/scenes/draw-map.ce.js";
6
9
  import { LoadMapToken } from "./services/loadMap.js";
7
10
  import { RpgSound } from "./Sound.js";
8
11
  import { RpgResource } from "./Resource.js";
9
- import { load } from "./node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/index.js";
12
+ import { load } from "./node_modules/.pnpm/@signe_sync@3.1.0_react@19.2.7/node_modules/@signe/sync/dist/index.js";
10
13
  import { RpgClientMap } from "./Game/Map.js";
11
14
  import { AnimationManager } from "./Game/AnimationManager.js";
12
15
  import { GlobalConfigToken } from "./module.js";
13
16
  import { PrebuiltComponentAnimations } from "./components/animations/index.js";
17
+ import __ce_component$2 from "./components/dynamics/text.ce.js";
18
+ import __ce_component$3 from "./components/dynamics/bar.ce.js";
19
+ import __ce_component$4 from "./components/dynamics/shape.ce.js";
20
+ import __ce_component$5 from "./components/dynamics/image.ce.js";
14
21
  import { NotificationManager } from "./Gui/NotificationManager.js";
22
+ import { normalizeRoomMapId } from "./utils/mapId.js";
23
+ import { ProjectileManager } from "./Game/ProjectileManager.js";
24
+ import { ClientVisualRegistry } from "./Game/ClientVisuals.js";
25
+ import { createClientPointerContext } from "./services/pointerContext.js";
26
+ import { RpgClientInteractions } from "./services/interactions.js";
27
+ import { EventComponentResolverRegistry } from "./Game/EventComponentResolver.js";
28
+ import { RpgClientBuiltinI18n } from "./i18n.js";
15
29
  import { Howl, bootstrapCanvas, signal, trigger } from "canvasengine";
16
- import { Direction, ModulesToken, PredictionController } from "@rpgjs/common";
30
+ import { Direction, ModulesToken, PredictionController, Vector2, getOrCreateI18nService, normalizeLightingState } from "@rpgjs/common";
17
31
  import { BehaviorSubject, combineLatest, filter, lastValueFrom, switchMap, take } from "rxjs";
18
32
  import * as PIXI from "pixi.js";
19
33
  //#region src/RpgClientEngine.ts
34
+ var DEFAULT_DASH_ADDITIONAL_SPEED = 8;
35
+ var DEFAULT_DASH_DURATION_MS = 180;
36
+ var DEFAULT_DASH_COOLDOWN_MS = 450;
37
+ var isDashInput = (input) => typeof input === "object" && input !== null && input.type === "dash";
38
+ var isMoveInput = (input) => typeof input === "object" && input !== null && input.type === "move";
39
+ var resolveMoveDirection = (input) => {
40
+ if (isMoveInput(input)) return input.direction;
41
+ if (typeof input === "string" || typeof input === "number") return input;
42
+ };
43
+ var directionToVector = (direction) => {
44
+ switch (direction) {
45
+ case Direction.Left: return {
46
+ x: -1,
47
+ y: 0
48
+ };
49
+ case Direction.Right: return {
50
+ x: 1,
51
+ y: 0
52
+ };
53
+ case Direction.Up: return {
54
+ x: 0,
55
+ y: -1
56
+ };
57
+ case Direction.Down:
58
+ default: return {
59
+ x: 0,
60
+ y: 1
61
+ };
62
+ }
63
+ };
64
+ var vectorToDirection = (direction) => {
65
+ if (Math.abs(direction.x) > Math.abs(direction.y)) return direction.x < 0 ? Direction.Left : Direction.Right;
66
+ return direction.y < 0 ? Direction.Up : Direction.Down;
67
+ };
68
+ var normalizeDashInput = (input, fallbackDirection) => {
69
+ const rawDirection = input.direction ?? directionToVector(fallbackDirection);
70
+ const rawX = Number(rawDirection?.x ?? 0);
71
+ const rawY = Number(rawDirection?.y ?? 0);
72
+ const magnitude = Math.hypot(rawX, rawY);
73
+ if (!Number.isFinite(magnitude) || magnitude <= 0) return null;
74
+ const additionalSpeed = typeof input.additionalSpeed === "number" && Number.isFinite(input.additionalSpeed) ? Math.max(0, Math.min(input.additionalSpeed, 64)) : DEFAULT_DASH_ADDITIONAL_SPEED;
75
+ const duration = typeof input.duration === "number" && Number.isFinite(input.duration) ? Math.max(1, Math.min(input.duration, 1e3)) : DEFAULT_DASH_DURATION_MS;
76
+ const cooldown = typeof input.cooldown === "number" && Number.isFinite(input.cooldown) ? Math.max(0, Math.min(input.cooldown, 5e3)) : DEFAULT_DASH_COOLDOWN_MS;
77
+ return {
78
+ type: "dash",
79
+ direction: {
80
+ x: rawX / magnitude,
81
+ y: rawY / magnitude
82
+ },
83
+ additionalSpeed,
84
+ duration,
85
+ cooldown
86
+ };
87
+ };
20
88
  var RpgClientEngine = class {
21
89
  constructor(context) {
22
90
  this.context = context;
91
+ this.sceneMapComponent = __ce_component$1;
23
92
  this.stopProcessingInput = false;
24
93
  this.width = signal("100%");
25
94
  this.height = signal("100%");
26
95
  this.spritesheets = /* @__PURE__ */ new Map();
96
+ this.spritesheetPromises = /* @__PURE__ */ new Map();
27
97
  this.sounds = /* @__PURE__ */ new Map();
28
98
  this.componentAnimations = [];
99
+ this.clientVisuals = new ClientVisualRegistry();
100
+ this.pointer = createClientPointerContext();
101
+ this.interactions = new RpgClientInteractions(this);
29
102
  this.particleSettings = { emitters: [] };
30
103
  this.playerIdSignal = signal(null);
31
104
  this.spriteComponentsBehind = signal([]);
32
105
  this.spriteComponentsInFront = signal([]);
106
+ this.spriteComponents = /* @__PURE__ */ new Map();
107
+ this.eventComponentResolvers = new EventComponentResolverRegistry();
33
108
  this.cameraFollowTargetId = signal(null);
109
+ this.cameraFollowSmoothMove = false;
110
+ this.cameraFollowRevision = signal(0);
34
111
  this.mapShakeTrigger = trigger();
35
112
  this.controlsReady = signal(void 0);
36
113
  this.gamePause = signal(false);
37
114
  this.predictionEnabled = false;
38
115
  this.SERVER_CORRECTION_THRESHOLD = 30;
116
+ this.localMovementAuthority = false;
117
+ this.lastLocalMovementInputAt = 0;
118
+ this.LOCAL_MOVEMENT_AUTHORITY_ACK_GRACE_MS = 250;
39
119
  this.inputFrameCounter = 0;
40
120
  this.pendingPredictionFrames = [];
41
121
  this.lastClientPhysicsStepAt = 0;
42
122
  this.frameOffset = 0;
123
+ this.latestServerTickAt = 0;
124
+ this.dashLockedUntil = 0;
43
125
  this.rtt = 0;
44
126
  this.pingInterval = null;
45
127
  this.PING_INTERVAL_MS = 5e3;
@@ -53,12 +135,20 @@ var RpgClientEngine = class {
53
135
  this.playersReceived$ = new BehaviorSubject(false);
54
136
  this.eventsReceived$ = new BehaviorSubject(false);
55
137
  this.sceneResetQueued = false;
138
+ this.mapTransitionInProgress = false;
139
+ this.socketListenersInitialized = false;
140
+ this.clientReadyForMapChanges = false;
141
+ this.pendingMapChanges = [];
56
142
  this.tickSubscriptions = [];
143
+ this.pendingSyncPackets = [];
57
144
  this.notificationManager = new NotificationManager();
58
145
  this.webSocket = inject(WebSocketToken);
59
146
  this.guiService = inject(RpgGui);
60
147
  this.loadMapService = inject(LoadMapToken);
61
148
  this.hooks = inject(ModulesToken);
149
+ this.i18nService = getOrCreateI18nService(context);
150
+ this.i18nService.addMessages(RpgClientBuiltinI18n, "rpgjs-client", 0);
151
+ this.projectiles = new ProjectileManager(this.hooks, (projectile) => this.predictProjectileImpact(projectile));
62
152
  this.globalConfig = inject(GlobalConfigToken);
63
153
  if (!this.globalConfig) this.globalConfig = {};
64
154
  if (!this.globalConfig.box) this.globalConfig.box = {
@@ -72,9 +162,38 @@ var RpgClientEngine = class {
72
162
  id: "animation",
73
163
  component: PrebuiltComponentAnimations.Animation
74
164
  });
165
+ this.registerSpriteComponent("rpg:text", __ce_component$2);
166
+ this.registerSpriteComponent("rpg:hpBar", __ce_component$3);
167
+ this.registerSpriteComponent("rpg:spBar", __ce_component$3);
168
+ this.registerSpriteComponent("rpg:bar", __ce_component$3);
169
+ this.registerSpriteComponent("rpg:shape", __ce_component$4);
170
+ this.registerSpriteComponent("rpg:image", __ce_component$5);
75
171
  this.predictionEnabled = this.globalConfig?.prediction?.enabled !== false;
172
+ this.localMovementAuthority = this.resolveLocalMovementAuthority();
76
173
  this.initializePredictionController();
77
174
  }
175
+ resolveLocalMovementAuthority() {
176
+ const predictionConfig = this.globalConfig?.prediction;
177
+ const configured = this.globalConfig?.movementAuthority ?? predictionConfig?.movementAuthority ?? predictionConfig?.authority ?? predictionConfig?.mode;
178
+ if (configured === "server" || configured === "network" || configured === false) return false;
179
+ if (configured === "client" || configured === "local" || configured === true) return true;
180
+ return this.webSocket.mode === "standalone";
181
+ }
182
+ setLocale(locale) {
183
+ this.locale = locale;
184
+ }
185
+ getLocale() {
186
+ return this.locale || this.i18nService.defaultLocale;
187
+ }
188
+ t(key, params) {
189
+ return this.i18nService.t(key, params, this.getLocale());
190
+ }
191
+ i18n() {
192
+ return {
193
+ locale: this.getLocale(),
194
+ t: (key, params) => this.t(key, params)
195
+ };
196
+ }
78
197
  /**
79
198
  * Assigns a CanvasEngine KeyboardControls instance to the dependency injection context
80
199
  *
@@ -114,18 +233,31 @@ var RpgClientEngine = class {
114
233
  ...currentValues,
115
234
  values: new Map([["__default__", controlInstance]])
116
235
  };
117
- this.controlsReady.set(void 0);
236
+ this.controlsReady.set(true);
118
237
  }
119
238
  async start() {
120
239
  this.sceneMap = new RpgClientMap();
121
240
  this.sceneMap.configureClientPrediction(this.predictionEnabled);
122
241
  this.sceneMap.loadPhysic();
242
+ this.resolveSceneMapComponent();
243
+ inject(SaveClientService).initialize();
244
+ this.initListeners();
245
+ this.guiService._initialize();
246
+ try {
247
+ await this.webSocket.connection();
248
+ } catch (error) {
249
+ this.stopPingPong();
250
+ await this.callConnectError(error);
251
+ throw error;
252
+ }
123
253
  this.selector = document.body.querySelector("#rpg");
124
254
  const bootstrapOptions = this.globalConfig?.bootstrapCanvasOptions;
125
- const { app, canvasElement } = await bootstrapCanvas(this.selector, component, bootstrapOptions);
255
+ const { app, canvasElement } = await bootstrapCanvas(this.selector, __ce_component, bootstrapOptions);
256
+ this.installCanvasResizeGuard(app);
126
257
  this.canvasApp = app;
127
258
  this.canvasElement = canvasElement;
128
259
  this.renderer = app.renderer;
260
+ this.setupPointerTracking();
129
261
  this.tick = canvasElement?.propObservables?.context["tick"].observable;
130
262
  const inputCheckSubscription = this.tick.subscribe(() => {
131
263
  if (Date.now() - this.lastInputTime > 100) {
@@ -137,6 +269,7 @@ var RpgClientEngine = class {
137
269
  this.tickSubscriptions.push(inputCheckSubscription);
138
270
  this.hooks.callHooks("client-spritesheets-load", this).subscribe();
139
271
  this.hooks.callHooks("client-spritesheetResolver-load", this).subscribe();
272
+ this.flushPendingSyncPackets();
140
273
  this.hooks.callHooks("client-sounds-load", this).subscribe();
141
274
  this.hooks.callHooks("client-soundResolver-load", this).subscribe();
142
275
  RpgSound.init(this);
@@ -144,14 +277,20 @@ var RpgClientEngine = class {
144
277
  this.hooks.callHooks("client-gui-load", this).subscribe();
145
278
  this.hooks.callHooks("client-particles-load", this).subscribe();
146
279
  this.hooks.callHooks("client-componentAnimations-load", this).subscribe();
280
+ this.hooks.callHooks("client-clientVisuals-load", this).subscribe();
281
+ this.hooks.callHooks("client-projectiles-load", this).subscribe();
282
+ this.hooks.callHooks("client-interactions-load", this).subscribe();
147
283
  this.hooks.callHooks("client-sprite-load", this).subscribe();
148
284
  await lastValueFrom(this.hooks.callHooks("client-engine-onStart", this));
285
+ this.clientReadyForMapChanges = true;
286
+ this.flushPendingMapChanges();
149
287
  this.resizeHandler = () => {
150
288
  this.hooks.callHooks("client-engine-onWindowResize", this).subscribe();
151
289
  };
152
290
  window.addEventListener("resize", this.resizeHandler);
153
291
  const tickSubscription = this.tick.subscribe((tick) => {
154
292
  this.stepClientPhysicsTick();
293
+ this.projectiles.step();
155
294
  this.flushPendingPredictedStates();
156
295
  this.flushPendingMovePath();
157
296
  this.hooks.callHooks("client-engine-onStep", this, tick).subscribe();
@@ -162,12 +301,138 @@ var RpgClientEngine = class {
162
301
  }
163
302
  });
164
303
  this.tickSubscriptions.push(tickSubscription);
165
- await this.webSocket.connection(() => {
166
- inject(SaveClientService).initialize(this.webSocket);
167
- this.initListeners();
168
- this.guiService._initialize();
169
- this.startPingPong();
170
- });
304
+ this.startPingPong();
305
+ }
306
+ installCanvasResizeGuard(app) {
307
+ if (!app || typeof app.resize !== "function") return;
308
+ const originalResize = app.resize.bind(app);
309
+ app.resize = () => {
310
+ const targetSize = this.readCanvasResizeTargetSize(app);
311
+ const rendererSize = this.readCanvasRendererSize(app);
312
+ if (targetSize && rendererSize && targetSize.width === rendererSize.width && targetSize.height === rendererSize.height) {
313
+ this.cancelCanvasResizeFrame(app);
314
+ return;
315
+ }
316
+ originalResize();
317
+ };
318
+ }
319
+ readCanvasResizeTargetSize(app) {
320
+ const resizeTarget = app?.resizeTo;
321
+ if (!resizeTarget || typeof window === "undefined") return null;
322
+ const rawWidth = resizeTarget === window ? window.innerWidth : resizeTarget.clientWidth;
323
+ const rawHeight = resizeTarget === window ? window.innerHeight : resizeTarget.clientHeight;
324
+ const width = Math.round(Number(rawWidth));
325
+ const height = Math.round(Number(rawHeight));
326
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width < 0 || height < 0) return null;
327
+ return {
328
+ width,
329
+ height
330
+ };
331
+ }
332
+ readCanvasRendererSize(app) {
333
+ const screen = app?.renderer?.screen;
334
+ const width = Math.round(Number(screen?.width));
335
+ const height = Math.round(Number(screen?.height));
336
+ if (!Number.isFinite(width) || !Number.isFinite(height)) return null;
337
+ return {
338
+ width,
339
+ height
340
+ };
341
+ }
342
+ cancelCanvasResizeFrame(app) {
343
+ if (typeof app?._cancelResize === "function") app._cancelResize();
344
+ }
345
+ resolveSceneMapComponent() {
346
+ const components = this.hooks.getHookFunctions("client-sceneMap-component");
347
+ const component = components[components.length - 1];
348
+ if (component) this.sceneMapComponent = component;
349
+ }
350
+ setupPointerTracking() {
351
+ const renderer = this.renderer;
352
+ const canvas = renderer?.canvas ?? renderer?.view ?? this.canvasApp?.canvas;
353
+ if (!canvas || typeof canvas.addEventListener !== "function") return;
354
+ this.pointerCanvas = canvas;
355
+ const updatePointer = (event) => {
356
+ const rect = canvas.getBoundingClientRect();
357
+ const screen = {
358
+ x: event.clientX - rect.left,
359
+ y: event.clientY - rect.top
360
+ };
361
+ const viewport = this.findViewportInstance();
362
+ let world = screen;
363
+ if (viewport && typeof viewport.toWorld === "function") {
364
+ const point = viewport.toWorld(screen.x, screen.y);
365
+ world = {
366
+ x: Number(point.x),
367
+ y: Number(point.y)
368
+ };
369
+ } else if (viewport && typeof viewport.toLocal === "function") {
370
+ const point = viewport.toLocal(screen);
371
+ world = {
372
+ x: Number(point.x),
373
+ y: Number(point.y)
374
+ };
375
+ }
376
+ this.pointer.update(screen, world);
377
+ };
378
+ this.pointerMoveHandler = (event) => {
379
+ updatePointer(event);
380
+ this.interactions.handlePointerMove(event);
381
+ };
382
+ this.pointerUpHandler = (event) => {
383
+ updatePointer(event);
384
+ this.interactions.handlePointerUp(event);
385
+ };
386
+ this.pointerCancelHandler = (event) => {
387
+ updatePointer(event);
388
+ this.interactions.cancelDrag(event);
389
+ };
390
+ canvas.addEventListener("pointermove", this.pointerMoveHandler);
391
+ canvas.addEventListener("pointerdown", this.pointerMoveHandler);
392
+ canvas.addEventListener("pointerup", this.pointerUpHandler);
393
+ canvas.addEventListener("pointercancel", this.pointerCancelHandler);
394
+ canvas.addEventListener("pointerleave", this.pointerCancelHandler);
395
+ }
396
+ updatePointerFromInteractionEvent(event) {
397
+ const global = event?.global ?? event?.data?.global;
398
+ if (!global) {
399
+ this.pointer.updateFromEvent(event);
400
+ return;
401
+ }
402
+ const screen = {
403
+ x: Number(global.x),
404
+ y: Number(global.y)
405
+ };
406
+ if (!Number.isFinite(screen.x) || !Number.isFinite(screen.y)) {
407
+ this.pointer.updateFromEvent(event);
408
+ return;
409
+ }
410
+ const viewport = this.findViewportInstance();
411
+ if (viewport && typeof viewport.toWorld === "function") {
412
+ const point = viewport.toWorld(screen.x, screen.y);
413
+ this.pointer.update(screen, {
414
+ x: Number(point.x),
415
+ y: Number(point.y)
416
+ });
417
+ return;
418
+ }
419
+ this.pointer.update(screen);
420
+ }
421
+ findViewportInstance() {
422
+ const find = (node) => {
423
+ if (!node) return void 0;
424
+ if (typeof node?.toWorld === "function" || node?.constructor?.name === "Viewport") return node;
425
+ for (const child of node.children ?? []) {
426
+ const viewport = find(child);
427
+ if (viewport) return viewport;
428
+ }
429
+ };
430
+ return find(this.canvasApp?.stage);
431
+ }
432
+ clearCameraFollowViewportPlugins() {
433
+ const viewport = this.findViewportInstance();
434
+ viewport?.plugins?.remove?.("animate");
435
+ viewport?.plugins?.remove?.("follow");
171
436
  }
172
437
  prepareSyncPayload(data) {
173
438
  const payload = { ...data ?? {} };
@@ -175,7 +440,8 @@ var RpgClientEngine = class {
175
440
  delete payload.timestamp;
176
441
  const myId = this.playerIdSignal();
177
442
  const players = payload.players;
178
- if (this.predictionEnabled && !!this.prediction?.hasPendingInputs() && myId && players && players[myId]) {
443
+ const localPatch = myId && players ? players[myId] : void 0;
444
+ if (this.shouldPreserveLocalPlayerPosition(localPatch) && myId && players && players[myId]) {
179
445
  const localPatch = { ...players[myId] };
180
446
  delete localPatch.x;
181
447
  delete localPatch.y;
@@ -188,6 +454,18 @@ var RpgClientEngine = class {
188
454
  }
189
455
  return payload;
190
456
  }
457
+ shouldPreserveLocalPlayerPosition(localPatch) {
458
+ if (!localPatch) return false;
459
+ if (this.predictionEnabled && !!this.prediction?.hasPendingInputs()) return true;
460
+ return this.shouldKeepLocalPlayerMovement();
461
+ }
462
+ shouldKeepLocalPlayerMovement() {
463
+ if (!this.localMovementAuthority || this.mapTransitionInProgress) return false;
464
+ const myId = this.playerIdSignal();
465
+ if (!(myId ? this.sceneMap?.players?.()?.[myId] : void 0)) return false;
466
+ if (this.prediction?.hasPendingInputs()) return true;
467
+ return Date.now() - this.lastLocalMovementInputAt <= this.LOCAL_MOVEMENT_AUTHORITY_ACK_GRACE_MS;
468
+ }
191
469
  normalizeAckWithSyncState(ack, syncData) {
192
470
  const myId = this.playerIdSignal();
193
471
  if (!myId) return ack;
@@ -201,43 +479,30 @@ var RpgClientEngine = class {
201
479
  };
202
480
  }
203
481
  initListeners() {
482
+ if (this.socketListenersInitialized) return;
483
+ this.socketListenersInitialized = true;
204
484
  this.webSocket.on("sync", (data) => {
205
- if (data.pId) {
206
- this.playerIdSignal.set(data.pId);
207
- this.playerIdReceived$.next(true);
208
- }
209
- if (this.sceneResetQueued) {
210
- this.sceneMap.reset();
211
- this.sceneMap.loadPhysic();
212
- this.sceneResetQueued = false;
213
- }
214
- this.hooks.callHooks("client-sceneMap-onChanges", this.sceneMap, { partial: data }).subscribe();
215
- const ack = data?.ack;
216
- const normalizedAck = ack && typeof ack.frame === "number" ? this.normalizeAckWithSyncState(ack, data) : void 0;
217
- const payload = this.prepareSyncPayload(data);
218
- load(this.sceneMap, payload, true);
219
- if (normalizedAck) this.applyServerAck(normalizedAck);
220
- for (const playerId in payload.players ?? {}) {
221
- const player = payload.players[playerId];
222
- if (!player._param) continue;
223
- for (const param in player._param) this.sceneMap.players()[playerId]._param()[param] = player._param[param];
485
+ if (!this.tick) {
486
+ this.pendingSyncPackets.push(data);
487
+ return;
224
488
  }
225
- const players = payload.players || this.sceneMap.players();
226
- if (players && Object.keys(players).length > 0) this.playersReceived$.next(true);
227
- if ((payload.events || this.sceneMap.events()) !== void 0) this.eventsReceived$.next(true);
489
+ this.applySyncPacket(data);
228
490
  });
229
491
  this.webSocket.on("pong", (data) => {
230
- this.rtt = Date.now() - data.clientTime;
492
+ const now = Date.now();
493
+ this.rtt = now - data.clientTime;
231
494
  const estimatedTicksInFlight = Math.floor(this.rtt / 2 / (1e3 / 60));
232
495
  const estimatedServerTickNow = data.serverTick + estimatedTicksInFlight;
496
+ this.updateServerTickEstimate(estimatedServerTickNow, now);
233
497
  if (this.inputFrameCounter > 0) this.frameOffset = estimatedServerTickNow - data.clientFrame;
234
498
  console.debug(`[Ping/Pong] RTT: ${this.rtt}ms, ServerTick: ${data.serverTick}, FrameOffset: ${this.frameOffset}`);
235
499
  });
236
500
  this.webSocket.on("changeMap", (data) => {
237
- this.sceneResetQueued = true;
238
- this.cameraFollowTargetId.set(null);
239
- const transferToken = typeof data?.transferToken === "string" ? data.transferToken : void 0;
240
- this.loadScene(data.mapId, transferToken);
501
+ if (!this.clientReadyForMapChanges) {
502
+ this.pendingMapChanges.push(data);
503
+ return;
504
+ }
505
+ this.handleChangeMap(data);
241
506
  });
242
507
  this.webSocket.on("showComponentAnimation", (data) => {
243
508
  const { params, object, position, id } = data;
@@ -245,14 +510,42 @@ var RpgClientEngine = class {
245
510
  const player = object ? this.sceneMap.getObjectById(object) : void 0;
246
511
  this.getComponentAnimation(id).displayEffect(params, player || position);
247
512
  });
513
+ this.webSocket.on("clientVisual", (data) => {
514
+ this.playClientVisual(data);
515
+ });
516
+ this.webSocket.on("projectile:spawnBatch", (data) => {
517
+ if (!this.shouldProcessProjectilePacket(data)) return;
518
+ this.projectiles.spawnBatch(data?.projectiles ?? [], {
519
+ mapId: data?.mapId,
520
+ currentServerTick: this.estimateServerTick(),
521
+ tickDurationMs: this.getPhysicsTickDurationMs()
522
+ });
523
+ });
524
+ this.webSocket.on("projectile:impactBatch", (data) => {
525
+ if (!this.shouldProcessProjectilePacket(data)) return;
526
+ this.projectiles.impactBatch(data?.impacts ?? [], { mapId: data?.mapId });
527
+ });
528
+ this.webSocket.on("projectile:destroyBatch", (data) => {
529
+ if (!this.shouldProcessProjectilePacket(data)) return;
530
+ this.projectiles.destroyBatch(data?.projectiles ?? [], { mapId: data?.mapId });
531
+ });
532
+ this.webSocket.on("projectile:clear", (data) => {
533
+ if (!this.shouldProcessProjectilePacket(data)) return;
534
+ this.projectiles.clear();
535
+ });
248
536
  this.webSocket.on("notification", (data) => {
249
537
  this.notificationManager.add(data);
250
538
  });
251
539
  this.webSocket.on("setAnimation", (data) => {
252
- const { animationName, nbTimes, object, graphic } = data;
253
- const player = this.sceneMap.getObjectById(object);
254
- if (graphic !== void 0) player.setAnimation(animationName, graphic, nbTimes);
255
- else player.setAnimation(animationName, nbTimes);
540
+ const { animationName, nbTimes, object, graphic, restoreAnimationName, restoreGraphics } = data;
541
+ const player = object ? this.sceneMap.getObjectById(object) : void 0;
542
+ if (!player) return;
543
+ const restoreOptions = {
544
+ restoreAnimationName,
545
+ restoreGraphics
546
+ };
547
+ if (graphic !== void 0) player.setAnimation(animationName, graphic, nbTimes, restoreOptions);
548
+ else player.setAnimation(animationName, nbTimes, restoreOptions);
256
549
  });
257
550
  this.webSocket.on("playSound", (data) => {
258
551
  const { soundId, volume, loop } = data;
@@ -314,6 +607,10 @@ var RpgClientEngine = class {
314
607
  seed: raw.seed
315
608
  });
316
609
  });
610
+ this.webSocket.on("lightingState", (data) => {
611
+ const raw = data && typeof data === "object" && "value" in data ? data.value : data;
612
+ this.sceneMap.lightingState.set(normalizeLightingState(raw));
613
+ });
317
614
  this.webSocket.on("open", () => {
318
615
  this.hooks.callHooks("client-engine-onConnected", this, this.socket).subscribe();
319
616
  this.startPingPong();
@@ -323,9 +620,82 @@ var RpgClientEngine = class {
323
620
  this.stopPingPong();
324
621
  });
325
622
  this.webSocket.on("error", (error) => {
326
- this.hooks.callHooks("client-engine-onConnectError", this, error, this.socket).subscribe();
623
+ this.callConnectError(error);
624
+ });
625
+ }
626
+ beginMapTransfer(nextMapId) {
627
+ this.mapTransitionInProgress = true;
628
+ this.currentMapRoomId = nextMapId;
629
+ this.sceneResetQueued = false;
630
+ this.clearClientPredictionStates();
631
+ this.sceneMap.weatherState.set(null);
632
+ this.sceneMap.lightingState.set(null);
633
+ this.sceneMap.clearLightSpots();
634
+ this.clearComponentAnimations();
635
+ this.projectiles.setMapId(nextMapId);
636
+ this.resetCameraFollow(false);
637
+ this.sceneMap.reset();
638
+ this.sceneMap.loadPhysic();
639
+ }
640
+ clearComponentAnimations() {
641
+ this.componentAnimations.forEach((componentAnimation) => {
642
+ componentAnimation.instance?.clear?.();
327
643
  });
328
644
  }
645
+ shouldProcessProjectilePacket(data) {
646
+ if (this.mapTransitionInProgress) return false;
647
+ const packetMapId = normalizeRoomMapId(typeof data?.mapId === "string" ? data.mapId : void 0);
648
+ const currentMapId = normalizeRoomMapId(this.currentMapRoomId);
649
+ return !packetMapId || !currentMapId || packetMapId === currentMapId;
650
+ }
651
+ async callConnectError(error) {
652
+ await lastValueFrom(this.hooks.callHooks("client-engine-onConnectError", this, error, this.socket));
653
+ }
654
+ flushPendingSyncPackets() {
655
+ const packets = this.pendingSyncPackets;
656
+ this.pendingSyncPackets = [];
657
+ packets.forEach((packet) => this.applySyncPacket(packet));
658
+ }
659
+ flushPendingMapChanges() {
660
+ const packets = this.pendingMapChanges;
661
+ this.pendingMapChanges = [];
662
+ packets.forEach((packet) => this.handleChangeMap(packet));
663
+ }
664
+ handleChangeMap(data) {
665
+ const nextMapId = typeof data?.mapId === "string" ? data.mapId : void 0;
666
+ this.beginMapTransfer(nextMapId);
667
+ const transferToken = typeof data?.transferToken === "string" ? data.transferToken : void 0;
668
+ this.loadScene(data.mapId, transferToken);
669
+ }
670
+ applySyncPacket(data) {
671
+ if (data.pId) {
672
+ this.playerIdSignal.set(data.pId);
673
+ this.playerIdReceived$.next(true);
674
+ }
675
+ if (this.sceneResetQueued) {
676
+ const weatherState = this.sceneMap.weatherState();
677
+ const lightingState = this.sceneMap.lightingState();
678
+ this.sceneMap.reset();
679
+ this.sceneMap.weatherState.set(weatherState);
680
+ this.sceneMap.lightingState.set(lightingState);
681
+ this.sceneMap.loadPhysic();
682
+ this.sceneResetQueued = false;
683
+ }
684
+ this.hooks.callHooks("client-sceneMap-onChanges", this.sceneMap, { partial: data }).subscribe();
685
+ const ack = data?.ack;
686
+ const normalizedAck = ack && typeof ack.frame === "number" ? this.normalizeAckWithSyncState(ack, data) : void 0;
687
+ const payload = this.prepareSyncPayload(data);
688
+ load(this.sceneMap, payload, true);
689
+ if (normalizedAck) this.applyServerAck(normalizedAck);
690
+ for (const playerId in payload.players ?? {}) {
691
+ const player = payload.players[playerId];
692
+ if (!player._param) continue;
693
+ for (const param in player._param) this.sceneMap.players()[playerId]._param()[param] = player._param[param];
694
+ }
695
+ const players = payload.players || this.sceneMap.players();
696
+ if (players && Object.keys(players).length > 0) this.playersReceived$.next(true);
697
+ if ((payload.events || this.sceneMap.events()) !== void 0) this.eventsReceived$.next(true);
698
+ }
329
699
  /**
330
700
  * Start periodic ping/pong for client-server synchronization
331
701
  *
@@ -402,18 +772,25 @@ var RpgClientEngine = class {
402
772
  room: mapId,
403
773
  query: transferToken ? { transferToken } : void 0
404
774
  });
405
- await this.webSocket.reconnect(() => {
406
- inject(SaveClientService).initialize(this.webSocket);
407
- this.initListeners();
408
- this.guiService._initialize();
409
- });
775
+ try {
776
+ await this.webSocket.reconnect();
777
+ } catch (error) {
778
+ this.mapTransitionInProgress = false;
779
+ this.stopPingPong();
780
+ await this.callConnectError(error);
781
+ throw error;
782
+ }
410
783
  const res = await this.loadMapService.load(mapId);
784
+ const loadedLighting = typeof res?.lighting !== "undefined" ? res.lighting : res?.data?.lighting;
785
+ if (typeof loadedLighting !== "undefined") this.sceneMap.lightingState.set(normalizeLightingState(loadedLighting));
411
786
  this.sceneMap.data.set(res);
412
787
  if (this.playerIdSignal()) this.playerIdReceived$.next(true);
413
788
  const players = this.sceneMap.players();
414
789
  if (players && Object.keys(players).length > 0) this.playersReceived$.next(true);
415
790
  if (this.sceneMap.events() !== void 0) this.eventsReceived$.next(true);
416
791
  this.mapLoadCompleted$.next(true);
792
+ this.currentMapRoomId = mapId;
793
+ this.mapTransitionInProgress = false;
417
794
  this.sceneMap.configureClientPrediction(this.predictionEnabled);
418
795
  this.sceneMap.loadPhysic();
419
796
  }
@@ -458,7 +835,7 @@ var RpgClientEngine = class {
458
835
  * If not found and a resolver is set, it calls the resolver to create the spritesheet.
459
836
  * The resolved spritesheet is automatically cached for future use.
460
837
  *
461
- * @param id - The spritesheet ID to retrieve
838
+ * @param id - The spritesheet ID or legacy tile ID to retrieve
462
839
  * @returns The spritesheet if found or created, or undefined if not found and no resolver
463
840
  * @returns Promise<any> if the resolver is asynchronous
464
841
  *
@@ -474,12 +851,20 @@ var RpgClientEngine = class {
474
851
  getSpriteSheet(id) {
475
852
  if (this.spritesheets.has(id)) return this.spritesheets.get(id);
476
853
  if (this.spritesheetResolver) {
854
+ if (this.spritesheetPromises.has(id)) return this.spritesheetPromises.get(id);
477
855
  const result = this.spritesheetResolver(id);
478
- if (result instanceof Promise) return result.then((spritesheet) => {
479
- if (spritesheet) this.spritesheets.set(id, spritesheet);
480
- return spritesheet;
481
- });
482
- else {
856
+ if (result instanceof Promise) {
857
+ const promise = result.then((spritesheet) => {
858
+ if (spritesheet) this.spritesheets.set(id, spritesheet);
859
+ this.spritesheetPromises.delete(id);
860
+ return spritesheet;
861
+ }).catch((error) => {
862
+ this.spritesheetPromises.delete(id);
863
+ throw error;
864
+ });
865
+ this.spritesheetPromises.set(id, promise);
866
+ return promise;
867
+ } else {
483
868
  if (result) this.spritesheets.set(id, result);
484
869
  return result;
485
870
  }
@@ -682,19 +1067,22 @@ var RpgClientEngine = class {
682
1067
  * Set the camera to follow a specific sprite
683
1068
  *
684
1069
  * This method changes which sprite the camera viewport should follow.
685
- * The camera will smoothly animate to the target sprite if smoothMove options are provided.
1070
+ * The camera can smoothly animate to the target sprite before continuous follow starts.
686
1071
  *
687
1072
  * ## Design
688
1073
  *
689
1074
  * The camera follow target is stored in a signal that is read by sprite components.
690
1075
  * Each sprite checks if it should be followed by comparing its ID with the target ID.
691
- * When smoothMove options are provided, the viewport animation is handled by CanvasEngine's
692
- * viewport system.
1076
+ * When smoothMove options are provided, the transition is handled by pixi-viewport's
1077
+ * animation plugin, then continuous follow is handled by CanvasEngine's viewport system.
693
1078
  *
694
1079
  * @param targetId - The ID of the sprite to follow. Set to null to follow the current player
695
1080
  * @param smoothMove - Animation options. Can be a boolean (default: true) or an object with time and ease
696
1081
  * @param smoothMove.time - Duration of the animation in milliseconds (optional)
697
1082
  * @param smoothMove.ease - Easing function name from https://easings.net (optional)
1083
+ * @param smoothMove.speed - Continuous follow speed after the transition (optional)
1084
+ * @param smoothMove.acceleration - Continuous follow acceleration after the transition (optional)
1085
+ * @param smoothMove.radius - Center radius where the target can move without moving the viewport (optional)
698
1086
  *
699
1087
  * @example
700
1088
  * ```ts
@@ -715,8 +1103,16 @@ var RpgClientEngine = class {
715
1103
  * ```
716
1104
  */
717
1105
  setCameraFollow(targetId, smoothMove) {
1106
+ this.clearCameraFollowViewportPlugins();
1107
+ this.cameraFollowSmoothMove = smoothMove ?? true;
718
1108
  this.cameraFollowTargetId.set(targetId);
719
- if (typeof smoothMove === "object" && smoothMove !== null) {}
1109
+ this.cameraFollowRevision.set(this.cameraFollowRevision() + 1);
1110
+ }
1111
+ resetCameraFollow(smoothMove = false) {
1112
+ this.clearCameraFollowViewportPlugins();
1113
+ this.cameraFollowSmoothMove = smoothMove;
1114
+ this.cameraFollowTargetId.set(null);
1115
+ this.cameraFollowRevision.set(this.cameraFollowRevision() + 1);
720
1116
  }
721
1117
  addParticle(particle) {
722
1118
  this.particleSettings.emitters.push(particle);
@@ -807,6 +1203,95 @@ var RpgClientEngine = class {
807
1203
  return component;
808
1204
  }
809
1205
  /**
1206
+ * Register a reusable sprite component that can be addressed by the server.
1207
+ *
1208
+ * Server-side component definitions only carry the component id and
1209
+ * serializable props. The client registry maps that id to the CanvasEngine
1210
+ * component that performs the actual rendering.
1211
+ *
1212
+ * @param id - Stable component id used by server component definitions
1213
+ * @param component - CanvasEngine component to render for this id
1214
+ * @returns The registered component
1215
+ *
1216
+ * @example
1217
+ * ```ts
1218
+ * engine.registerSpriteComponent('guildBadge', GuildBadgeComponent);
1219
+ * ```
1220
+ */
1221
+ registerSpriteComponent(id, component) {
1222
+ this.spriteComponents.set(id, component);
1223
+ return component;
1224
+ }
1225
+ /**
1226
+ * Get a reusable sprite component by id.
1227
+ *
1228
+ * @param id - Component id registered on the client
1229
+ * @returns The CanvasEngine component, or undefined when missing
1230
+ */
1231
+ getSpriteComponent(id) {
1232
+ return this.spriteComponents.get(id);
1233
+ }
1234
+ /**
1235
+ * Register a custom event component resolver.
1236
+ *
1237
+ * The last resolver returning a component wins. This lets later modules
1238
+ * override earlier defaults without replacing the whole map scene.
1239
+ *
1240
+ * @param resolver - Function receiving the synced event object
1241
+ * @returns The registered resolver
1242
+ */
1243
+ addEventComponentResolver(resolver) {
1244
+ return this.eventComponentResolvers.add(resolver);
1245
+ }
1246
+ /**
1247
+ * Resolve the custom CanvasEngine component for an event, if any.
1248
+ *
1249
+ * @param event - Synced client event object
1250
+ * @returns The component/config returned by the last matching resolver
1251
+ */
1252
+ resolveEventComponent(event) {
1253
+ return this.eventComponentResolvers.resolve(event);
1254
+ }
1255
+ registerProjectileComponent(type, component) {
1256
+ return this.projectiles.register(type, component);
1257
+ }
1258
+ getProjectileComponent(type) {
1259
+ return this.projectiles.get(type);
1260
+ }
1261
+ /**
1262
+ * Register a named client visual macro.
1263
+ *
1264
+ * Client visuals are small client-side functions that group existing visual
1265
+ * primitives such as flash, sound, component animations, sprite animation, or
1266
+ * map shake. The server sends only the visual name and a serializable payload.
1267
+ *
1268
+ * @param name - Stable visual name sent by the server
1269
+ * @param handler - Client-side visual handler
1270
+ * @returns The registered handler
1271
+ */
1272
+ registerClientVisual(name, handler) {
1273
+ return this.clientVisuals.register(name, handler);
1274
+ }
1275
+ /**
1276
+ * Register several named client visual macros.
1277
+ *
1278
+ * @param visuals - Map of visual names to client-side handlers
1279
+ */
1280
+ registerClientVisuals(visuals) {
1281
+ this.clientVisuals.registerMany(visuals);
1282
+ }
1283
+ /**
1284
+ * Play a registered client visual locally.
1285
+ *
1286
+ * This is also used by the websocket listener when the server calls
1287
+ * `player.clientVisual()` or `map.clientVisual()`.
1288
+ *
1289
+ * @param packet - Visual name and serializable payload
1290
+ */
1291
+ playClientVisual(packet) {
1292
+ return this.clientVisuals.play(packet, this);
1293
+ }
1294
+ /**
810
1295
  * Add a component animation to the engine
811
1296
  *
812
1297
  * Component animations are temporary visual effects that can be displayed
@@ -882,18 +1367,47 @@ var RpgClientEngine = class {
882
1367
  * duration: 1000,
883
1368
  * onFinish: () => console.log('Fade complete')
884
1369
  * });
1370
+ *
1371
+ * // Wait until the transition component calls onFinish
1372
+ * await engine.startTransition('fade', { duration: 1000 });
885
1373
  * ```
886
1374
  */
887
1375
  startTransition(id, props = {}) {
888
1376
  if (!this.guiService.exists(id)) throw new Error(`Transition with id ${id} not found. Make sure to add it using engine.addTransition() or in your module's transitions property.`);
889
- this.guiService.display(id, props);
1377
+ return new Promise((resolve) => {
1378
+ let finished = false;
1379
+ const finish = (data) => {
1380
+ if (finished) return;
1381
+ finished = true;
1382
+ props?.onFinish?.(data);
1383
+ resolve();
1384
+ };
1385
+ this.guiService.display(id, {
1386
+ ...props,
1387
+ onFinish: finish
1388
+ });
1389
+ });
890
1390
  }
891
1391
  async processInput({ input }) {
1392
+ if (this.stopProcessingInput) return;
1393
+ const currentPlayer = this.sceneMap.getCurrentPlayer();
1394
+ if (!(!currentPlayer || getCanMoveValue(currentPlayer))) {
1395
+ this.interruptCurrentPlayerMovement(currentPlayer);
1396
+ return;
1397
+ }
892
1398
  const timestamp = Date.now();
1399
+ const movementInput = isDashInput(input) ? normalizeDashInput(input, currentPlayer?.direction?.()) : input;
1400
+ if (!movementInput) return;
1401
+ if (isDashInput(movementInput)) {
1402
+ const cooldown = movementInput.cooldown ?? DEFAULT_DASH_COOLDOWN_MS;
1403
+ if (timestamp < this.dashLockedUntil) return;
1404
+ this.dashLockedUntil = timestamp + cooldown;
1405
+ }
1406
+ this.lastLocalMovementInputAt = timestamp;
893
1407
  let frame;
894
1408
  let tick;
895
1409
  if (this.predictionEnabled && this.prediction) {
896
- const meta = this.prediction.recordInput(input, timestamp);
1410
+ const meta = this.prediction.recordInput(movementInput, timestamp);
897
1411
  frame = meta.frame;
898
1412
  tick = meta.tick;
899
1413
  } else {
@@ -902,29 +1416,38 @@ var RpgClientEngine = class {
902
1416
  }
903
1417
  this.inputFrameCounter = frame;
904
1418
  this.hooks.callHooks("client-engine-onInput", this, {
905
- input,
1419
+ input: movementInput,
906
1420
  playerId: this.playerId
907
1421
  }).subscribe();
908
- const currentPlayer = this.sceneMap.getCurrentPlayer();
909
1422
  const bodyReady = this.ensureCurrentPlayerBody();
910
1423
  if (currentPlayer && bodyReady) {
911
- currentPlayer.changeDirection(input);
912
- this.sceneMap.moveBody(currentPlayer, input);
1424
+ this.applyPredictedMovementInput(currentPlayer, movementInput);
913
1425
  if (this.predictionEnabled && this.prediction) {
914
1426
  this.pendingPredictionFrames.push(frame);
915
1427
  if (this.pendingPredictionFrames.length > 240) this.pendingPredictionFrames = this.pendingPredictionFrames.slice(-240);
916
1428
  }
917
1429
  }
918
- this.emitMovePacket(input, frame, tick, timestamp, true);
919
- this.lastInputTime = Date.now();
1430
+ this.emitMovePacket(movementInput, frame, tick, timestamp, true);
1431
+ this.lastInputTime = isDashInput(movementInput) ? Date.now() + (movementInput.duration ?? DEFAULT_DASH_DURATION_MS) : Date.now();
920
1432
  }
921
- processAction({ action }) {
1433
+ async processDash(input = {}) {
1434
+ const currentPlayer = this.sceneMap.getCurrentPlayer();
1435
+ const dashInput = normalizeDashInput(input, typeof currentPlayer?.direction === "function" ? currentPlayer.direction() : currentPlayer?.direction);
1436
+ if (!dashInput) return;
1437
+ await this.processInput({ input: dashInput });
1438
+ }
1439
+ processAction(action, data) {
922
1440
  if (this.stopProcessingInput) return;
1441
+ const currentPlayer = this.sceneMap.getCurrentPlayer();
1442
+ if (!(!currentPlayer || getCanMoveValue(currentPlayer))) return;
1443
+ const payload = normalizeActionInput(action, data);
923
1444
  this.hooks.callHooks("client-engine-onInput", this, {
924
- input: "action",
1445
+ input: payload.action,
1446
+ action: payload.action,
1447
+ data: payload.data,
925
1448
  playerId: this.playerId
926
1449
  }).subscribe();
927
- this.webSocket.emit("action", { action });
1450
+ this.webSocket.emit("action", payload);
928
1451
  }
929
1452
  get PIXI() {
930
1453
  return PIXI;
@@ -938,9 +1461,43 @@ var RpgClientEngine = class {
938
1461
  get scene() {
939
1462
  return this.sceneMap;
940
1463
  }
1464
+ getObjectById(id) {
1465
+ return this.sceneMap?.getObjectById(id);
1466
+ }
941
1467
  getPhysicsTick() {
942
1468
  return this.sceneMap?.getTick?.() ?? 0;
943
1469
  }
1470
+ getPhysicsTickDurationMs() {
1471
+ const timeStep = this.sceneMap?.physic?.getWorld?.()?.getTimeStep?.();
1472
+ return typeof timeStep === "number" && Number.isFinite(timeStep) && timeStep > 0 ? timeStep * 1e3 : 1e3 / 60;
1473
+ }
1474
+ updateServerTickEstimate(serverTick, now = Date.now()) {
1475
+ if (typeof serverTick !== "number" || !Number.isFinite(serverTick)) return;
1476
+ this.latestServerTick = serverTick;
1477
+ this.latestServerTickAt = now;
1478
+ }
1479
+ estimateServerTick(now = Date.now()) {
1480
+ if (typeof this.latestServerTick !== "number" || this.latestServerTickAt <= 0) return;
1481
+ const elapsedTicks = Math.max(0, (now - this.latestServerTickAt) / this.getPhysicsTickDurationMs());
1482
+ return this.latestServerTick + elapsedTicks;
1483
+ }
1484
+ predictProjectileImpact(projectile) {
1485
+ if (projectile.predictImpact === false) return null;
1486
+ const sceneMap = this.sceneMap;
1487
+ if (!sceneMap?.physic || !Number.isFinite(projectile.range) || projectile.range <= 0) return null;
1488
+ const origin = projectile.origin;
1489
+ const direction = projectile.direction;
1490
+ if (!origin || !direction || !Number.isFinite(origin.x) || !Number.isFinite(origin.y) || !Number.isFinite(direction.x) || !Number.isFinite(direction.y) || direction.x === 0 && direction.y === 0) return null;
1491
+ const hit = sceneMap.physic.raycast(new Vector2(origin.x, origin.y), new Vector2(direction.x, direction.y), projectile.range, projectile.collisionMask, (entity) => projectile.ignoreOwner === false || !projectile.ownerId || entity.uuid !== projectile.ownerId);
1492
+ if (!hit) return null;
1493
+ return {
1494
+ id: projectile.id,
1495
+ targetId: hit.entity.uuid,
1496
+ x: hit.point.x,
1497
+ y: hit.point.y,
1498
+ distance: hit.distance
1499
+ };
1500
+ }
944
1501
  ensureCurrentPlayerBody() {
945
1502
  const player = this.sceneMap?.getCurrentPlayer();
946
1503
  const myId = this.playerIdSignal();
@@ -986,7 +1543,7 @@ var RpgClientEngine = class {
986
1543
  input: entry.direction,
987
1544
  x: state.x,
988
1545
  y: state.y,
989
- direction: state.direction ?? entry.direction
1546
+ direction: state.direction ?? resolveMoveDirection(entry.direction)
990
1547
  });
991
1548
  }
992
1549
  if (trajectory.length > this.MAX_MOVE_TRAJECTORY_POINTS) return trajectory.slice(-this.MAX_MOVE_TRAJECTORY_POINTS);
@@ -1008,6 +1565,11 @@ var RpgClientEngine = class {
1008
1565
  }
1009
1566
  flushPendingMovePath() {
1010
1567
  if (!this.predictionEnabled || !this.prediction) return;
1568
+ const player = this.sceneMap?.getCurrentPlayer?.();
1569
+ if (player && !getCanMoveValue(player)) {
1570
+ this.interruptCurrentPlayerMovement(player);
1571
+ return;
1572
+ }
1011
1573
  const pendingInputs = this.prediction.getPendingInputs();
1012
1574
  if (pendingInputs.length === 0) return;
1013
1575
  const latest = pendingInputs[pendingInputs.length - 1];
@@ -1016,6 +1578,17 @@ var RpgClientEngine = class {
1016
1578
  if (now - this.lastMovePathSentAt < this.MOVE_PATH_RESEND_INTERVAL_MS) return;
1017
1579
  this.emitMovePacket(latest.direction, latest.frame, latest.tick, now, false);
1018
1580
  }
1581
+ applyPredictedMovementInput(player, input) {
1582
+ if (isDashInput(input)) {
1583
+ const direction = vectorToDirection(input.direction);
1584
+ player.changeDirection(direction);
1585
+ return Boolean(this.sceneMap.dashBody?.(player, input));
1586
+ }
1587
+ const direction = resolveMoveDirection(input);
1588
+ if (!direction) return false;
1589
+ player.changeDirection(direction);
1590
+ return Boolean(this.sceneMap.moveBody?.(player, direction));
1591
+ }
1019
1592
  getLocalPlayerState() {
1020
1593
  const currentPlayer = this.sceneMap?.getCurrentPlayer();
1021
1594
  if (!currentPlayer) return {
@@ -1124,6 +1697,31 @@ var RpgClientEngine = class {
1124
1697
  this.lastMovePathSentFrame = 0;
1125
1698
  }
1126
1699
  /**
1700
+ * Stop local movement immediately and discard pending predicted movement.
1701
+ *
1702
+ * Use this before a blocking action such as an A-RPG attack, dialog, dash
1703
+ * startup, or any client-side state where already buffered movement inputs
1704
+ * must not be replayed after server reconciliation.
1705
+ *
1706
+ * @param player - Player object to stop. Defaults to the current player.
1707
+ * @returns `true` when a player was found and interrupted.
1708
+ *
1709
+ * @example
1710
+ * ```ts
1711
+ * engine.interruptCurrentPlayerMovement();
1712
+ * ```
1713
+ */
1714
+ interruptCurrentPlayerMovement(player = this.sceneMap?.getCurrentPlayer?.()) {
1715
+ if (!player) return false;
1716
+ this.sceneMap?.stopMovement?.(player);
1717
+ this.prediction?.clearPendingInputs();
1718
+ this.pendingPredictionFrames = [];
1719
+ this.lastInputTime = 0;
1720
+ this.lastMovePathSentAt = Date.now();
1721
+ this.lastMovePathSentFrame = this.inputFrameCounter;
1722
+ return true;
1723
+ }
1724
+ /**
1127
1725
  * Trigger a flash animation on a sprite
1128
1726
  *
1129
1727
  * This method allows you to trigger a flash effect on any sprite from client-side code.
@@ -1177,11 +1775,13 @@ var RpgClientEngine = class {
1177
1775
  if (sprite && typeof sprite.flash === "function") sprite.flash(options);
1178
1776
  }
1179
1777
  applyServerAck(ack) {
1778
+ this.updateServerTickEstimate(ack.serverTick);
1779
+ const keepLocalMovement = this.shouldKeepLocalPlayerMovement();
1180
1780
  if (this.predictionEnabled && this.prediction) {
1181
1781
  const result = this.prediction.applyServerAck({
1182
1782
  frame: ack.frame,
1183
1783
  serverTick: ack.serverTick,
1184
- state: typeof ack.x === "number" && typeof ack.y === "number" ? {
1784
+ state: !keepLocalMovement && typeof ack.x === "number" && typeof ack.y === "number" ? {
1185
1785
  x: ack.x,
1186
1786
  y: ack.y,
1187
1787
  direction: ack.direction
@@ -1191,6 +1791,7 @@ var RpgClientEngine = class {
1191
1791
  return;
1192
1792
  }
1193
1793
  if (typeof ack.x !== "number" || typeof ack.y !== "number") return;
1794
+ if (keepLocalMovement) return;
1194
1795
  const player = this.getCurrentPlayer();
1195
1796
  const myId = this.playerIdSignal();
1196
1797
  if (!player || !myId) return;
@@ -1205,13 +1806,17 @@ var RpgClientEngine = class {
1205
1806
  reconcilePrediction(authoritativeState, pendingInputs) {
1206
1807
  const player = this.getCurrentPlayer();
1207
1808
  if (!player) return;
1809
+ if (!getCanMoveValue(player)) {
1810
+ this.interruptCurrentPlayerMovement(player);
1811
+ return;
1812
+ }
1208
1813
  this.sceneMap.stopMovement(player);
1209
1814
  this.applyAuthoritativeState(authoritativeState);
1210
1815
  if (!pendingInputs.length) return;
1211
1816
  const replayInputs = pendingInputs.slice(-600);
1212
1817
  for (const entry of replayInputs) {
1213
1818
  if (!entry?.direction) continue;
1214
- this.sceneMap.moveBody(player, entry.direction);
1819
+ this.applyPredictedMovementInput(player, entry.direction);
1215
1820
  this.sceneMap.stepPredictionTick();
1216
1821
  this.prediction?.attachPredictedState(entry.frame, this.getLocalPlayerState());
1217
1822
  }
@@ -1277,6 +1882,19 @@ var RpgClientEngine = class {
1277
1882
  window.removeEventListener("resize", this.resizeHandler);
1278
1883
  this.resizeHandler = void 0;
1279
1884
  }
1885
+ if (this.pointerMoveHandler && this.pointerCanvas) {
1886
+ this.pointerCanvas.removeEventListener("pointermove", this.pointerMoveHandler);
1887
+ this.pointerCanvas.removeEventListener("pointerdown", this.pointerMoveHandler);
1888
+ if (this.pointerUpHandler) this.pointerCanvas.removeEventListener("pointerup", this.pointerUpHandler);
1889
+ if (this.pointerCancelHandler) {
1890
+ this.pointerCanvas.removeEventListener("pointercancel", this.pointerCancelHandler);
1891
+ this.pointerCanvas.removeEventListener("pointerleave", this.pointerCancelHandler);
1892
+ }
1893
+ this.pointerMoveHandler = void 0;
1894
+ this.pointerUpHandler = void 0;
1895
+ this.pointerCancelHandler = void 0;
1896
+ this.pointerCanvas = void 0;
1897
+ }
1280
1898
  const rendererStillExists = this.renderer && typeof this.renderer.destroy === "function";
1281
1899
  if (this.canvasApp && typeof this.canvasApp.destroy === "function") {
1282
1900
  try {
@@ -1305,9 +1923,10 @@ var RpgClientEngine = class {
1305
1923
  }
1306
1924
  if (this.prediction) this.prediction = void 0;
1307
1925
  this.playerIdSignal.set(null);
1308
- this.cameraFollowTargetId.set(null);
1926
+ this.resetCameraFollow(false);
1309
1927
  this.spriteComponentsBehind.set([]);
1310
1928
  this.spriteComponentsInFront.set([]);
1929
+ this.eventComponentResolvers.clear();
1311
1930
  this.spritesheets.clear();
1312
1931
  this.sounds.clear();
1313
1932
  this.componentAnimations = [];