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

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 (314) hide show
  1. package/CHANGELOG.md +123 -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 +96 -54
  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 +176 -11
  36. package/dist/RpgClientEngine.js +669 -68
  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 +606 -248
  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.js +2 -2
  90. package/dist/components/gui/mobile/index.js.map +1 -1
  91. package/dist/components/gui/mobile/mobile.ce.js +7 -5
  92. package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
  93. package/dist/components/gui/notification/notification.ce.js +28 -26
  94. package/dist/components/gui/notification/notification.ce.js.map +1 -1
  95. package/dist/components/gui/save-load.ce.js +81 -256
  96. package/dist/components/gui/save-load.ce.js.map +1 -1
  97. package/dist/components/gui/shop/shop.ce.js +107 -142
  98. package/dist/components/gui/shop/shop.ce.js.map +1 -1
  99. package/dist/components/gui/title-screen.ce.js +50 -73
  100. package/dist/components/gui/title-screen.ce.js.map +1 -1
  101. package/dist/components/index.d.ts +2 -1
  102. package/dist/components/index.js +1 -0
  103. package/dist/components/interaction-components.ce.js +22 -0
  104. package/dist/components/interaction-components.ce.js.map +1 -0
  105. package/dist/components/player-components-utils.d.ts +67 -0
  106. package/dist/components/player-components-utils.js +162 -0
  107. package/dist/components/player-components-utils.js.map +1 -0
  108. package/dist/components/player-components-utils.spec.d.ts +1 -0
  109. package/dist/components/player-components.ce.js +191 -0
  110. package/dist/components/player-components.ce.js.map +1 -0
  111. package/dist/components/prebuilt/hp-bar.ce.js +45 -45
  112. package/dist/components/prebuilt/hp-bar.ce.js.map +1 -1
  113. package/dist/components/prebuilt/light-halo.ce.js +39 -60
  114. package/dist/components/prebuilt/light-halo.ce.js.map +1 -1
  115. package/dist/components/scenes/canvas.ce.js +205 -27
  116. package/dist/components/scenes/canvas.ce.js.map +1 -1
  117. package/dist/components/scenes/draw-map.ce.js +39 -39
  118. package/dist/components/scenes/draw-map.ce.js.map +1 -1
  119. package/dist/components/scenes/event-layer.ce.js +51 -10
  120. package/dist/components/scenes/event-layer.ce.js.map +1 -1
  121. package/dist/core/inject.js +1 -1
  122. package/dist/core/inject.js.map +1 -1
  123. package/dist/core/setup.js +1 -1
  124. package/dist/core/setup.js.map +1 -1
  125. package/dist/decorators/spritesheet.d.ts +1 -0
  126. package/dist/decorators/spritesheet.js +11 -0
  127. package/dist/decorators/spritesheet.js.map +1 -0
  128. package/dist/i18n.d.ts +55 -0
  129. package/dist/i18n.js +60 -0
  130. package/dist/i18n.js.map +1 -0
  131. package/dist/i18n.spec.d.ts +1 -0
  132. package/dist/index.d.ts +7 -0
  133. package/dist/index.js +29 -21
  134. package/dist/module.js +45 -4
  135. package/dist/module.js.map +1 -1
  136. package/dist/node_modules/.pnpm/{@signe_di@2.9.0 → @signe_di@3.1.0}/node_modules/@signe/di/dist/index.js +7 -117
  137. package/dist/node_modules/.pnpm/@signe_di@3.1.0/node_modules/@signe/di/dist/index.js.map +1 -0
  138. package/dist/node_modules/.pnpm/@signe_reactive@3.1.0/node_modules/@signe/reactive/dist/index.js +239 -0
  139. package/dist/node_modules/.pnpm/@signe_reactive@3.1.0/node_modules/@signe/reactive/dist/index.js.map +1 -0
  140. package/dist/node_modules/.pnpm/@signe_room@3.1.0_react@19.2.7/node_modules/@signe/room/dist/chunk-EUXUH3YW.js +13 -0
  141. 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
  142. package/dist/node_modules/.pnpm/@signe_room@3.1.0_react@19.2.7/node_modules/@signe/room/dist/index.js +698 -0
  143. package/dist/node_modules/.pnpm/@signe_room@3.1.0_react@19.2.7/node_modules/@signe/room/dist/index.js.map +1 -0
  144. package/dist/node_modules/.pnpm/@signe_sync@3.1.0_react@19.2.7/node_modules/@signe/sync/dist/client/index.js +43 -0
  145. 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
  146. 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
  147. package/dist/node_modules/.pnpm/@signe_sync@3.1.0_react@19.2.7/node_modules/@signe/sync/dist/index.js.map +1 -0
  148. 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
  149. package/dist/node_modules/.pnpm/partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/index.js.map +1 -0
  150. 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
  151. package/dist/node_modules/.pnpm/partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/ws.js.map +1 -0
  152. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/index.js +13 -0
  153. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/index.js.map +1 -0
  154. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.js +106 -0
  155. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.js.map +1 -0
  156. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.js +13 -0
  157. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.js.map +1 -0
  158. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js +121 -0
  159. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js.map +1 -0
  160. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js +10 -0
  161. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js.map +1 -0
  162. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js +110 -0
  163. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js.map +1 -0
  164. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js +99 -0
  165. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js.map +1 -0
  166. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/locales/en.js +82 -0
  167. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/locales/en.js.map +1 -0
  168. 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
  169. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js.map +1 -0
  170. package/dist/presets/animation.js.map +1 -1
  171. package/dist/presets/faceset.js.map +1 -1
  172. package/dist/presets/icon.js.map +1 -1
  173. package/dist/presets/index.js.map +1 -1
  174. package/dist/presets/lpc.js.map +1 -1
  175. package/dist/presets/rmspritesheet.js.map +1 -1
  176. package/dist/services/AbstractSocket.d.ts +2 -0
  177. package/dist/services/AbstractSocket.js.map +1 -1
  178. package/dist/services/actionInput.d.ts +14 -0
  179. package/dist/services/actionInput.js +59 -0
  180. package/dist/services/actionInput.js.map +1 -0
  181. package/dist/services/actionInput.spec.d.ts +1 -0
  182. package/dist/services/interactions.d.ts +159 -0
  183. package/dist/services/interactions.js +460 -0
  184. package/dist/services/interactions.js.map +1 -0
  185. package/dist/services/interactions.spec.d.ts +1 -0
  186. package/dist/services/keyboardControls.d.ts +1 -0
  187. package/dist/services/keyboardControls.js +1 -0
  188. package/dist/services/keyboardControls.js.map +1 -1
  189. package/dist/services/loadMap.d.ts +9 -0
  190. package/dist/services/loadMap.js +1 -1
  191. package/dist/services/loadMap.js.map +1 -1
  192. package/dist/services/mmorpg-connection.d.ts +5 -0
  193. package/dist/services/mmorpg-connection.js +50 -0
  194. package/dist/services/mmorpg-connection.js.map +1 -0
  195. package/dist/services/mmorpg-connection.spec.d.ts +1 -0
  196. package/dist/services/mmorpg.d.ts +11 -4
  197. package/dist/services/mmorpg.js +57 -33
  198. package/dist/services/mmorpg.js.map +1 -1
  199. package/dist/services/pointerContext.d.ts +11 -0
  200. package/dist/services/pointerContext.js +48 -0
  201. package/dist/services/pointerContext.js.map +1 -0
  202. package/dist/services/pointerContext.spec.d.ts +1 -0
  203. package/dist/services/save.js.map +1 -1
  204. package/dist/services/save.spec.d.ts +1 -0
  205. package/dist/services/standalone-message.d.ts +1 -0
  206. package/dist/services/standalone-message.js +9 -0
  207. package/dist/services/standalone-message.js.map +1 -0
  208. package/dist/services/standalone.d.ts +4 -1
  209. package/dist/services/standalone.js +36 -16
  210. package/dist/services/standalone.js.map +1 -1
  211. package/dist/services/standalone.spec.d.ts +1 -0
  212. package/dist/utils/getEntityProp.js +4 -3
  213. package/dist/utils/getEntityProp.js.map +1 -1
  214. package/dist/utils/getEntityProp.spec.d.ts +1 -0
  215. package/dist/utils/mapId.d.ts +1 -0
  216. package/dist/utils/mapId.js +6 -0
  217. package/dist/utils/mapId.js.map +1 -0
  218. package/dist/utils/readPropValue.d.ts +2 -0
  219. package/dist/utils/readPropValue.js +13 -0
  220. package/dist/utils/readPropValue.js.map +1 -0
  221. package/package.json +13 -13
  222. package/src/Game/AnimationManager.spec.ts +30 -0
  223. package/src/Game/AnimationManager.ts +26 -10
  224. package/src/Game/ClientVisuals.spec.ts +56 -0
  225. package/src/Game/ClientVisuals.ts +184 -0
  226. package/src/Game/EventComponentResolver.spec.ts +84 -0
  227. package/src/Game/EventComponentResolver.ts +74 -0
  228. package/src/Game/Map.ts +134 -2
  229. package/src/Game/Object.spec.ts +59 -0
  230. package/src/Game/Object.ts +181 -77
  231. package/src/Game/ProjectileManager.spec.ts +449 -0
  232. package/src/Game/ProjectileManager.ts +346 -0
  233. package/src/Gui/Gui.spec.ts +340 -0
  234. package/src/Gui/Gui.ts +129 -57
  235. package/src/Resource.ts +1 -2
  236. package/src/RpgClient.ts +220 -17
  237. package/src/RpgClientEngine.ts +959 -116
  238. package/src/components/animations/fx.ce +101 -0
  239. package/src/components/animations/index.ts +4 -2
  240. package/src/components/character.ce +681 -45
  241. package/src/components/dynamics/bar.ce +88 -0
  242. package/src/components/dynamics/image.ce +21 -0
  243. package/src/components/dynamics/parse-value.spec.ts +83 -0
  244. package/src/components/dynamics/parse-value.ts +111 -37
  245. package/src/components/dynamics/shape-utils.spec.ts +46 -0
  246. package/src/components/dynamics/shape-utils.ts +61 -0
  247. package/src/components/dynamics/shape.ce +90 -0
  248. package/src/components/dynamics/text.ce +35 -149
  249. package/src/components/gui/dialogbox/index.ce +52 -21
  250. package/src/components/gui/gameover.ce +6 -4
  251. package/src/components/gui/menu/equip-menu.ce +11 -9
  252. package/src/components/gui/menu/exit-menu.ce +6 -4
  253. package/src/components/gui/menu/items-menu.ce +11 -9
  254. package/src/components/gui/menu/main-menu.ce +14 -12
  255. package/src/components/gui/menu/options-menu.ce +4 -3
  256. package/src/components/gui/menu/skills-menu.ce +2 -1
  257. package/src/components/gui/notification/notification.ce +7 -1
  258. package/src/components/gui/save-load.ce +13 -11
  259. package/src/components/gui/shop/shop.ce +20 -18
  260. package/src/components/gui/title-screen.ce +6 -4
  261. package/src/components/index.ts +2 -1
  262. package/src/components/interaction-components.ce +23 -0
  263. package/src/components/player-components-utils.spec.ts +109 -0
  264. package/src/components/player-components-utils.ts +205 -0
  265. package/src/components/player-components.ce +222 -0
  266. package/src/components/prebuilt/hp-bar.ce +4 -3
  267. package/src/components/prebuilt/light-halo.ce +2 -2
  268. package/src/components/scenes/canvas.ce +219 -15
  269. package/src/components/scenes/draw-map.ce +34 -22
  270. package/src/components/scenes/event-layer.ce +55 -4
  271. package/src/core/setup.ts +2 -2
  272. package/src/decorators/spritesheet.ts +8 -0
  273. package/src/i18n.spec.ts +39 -0
  274. package/src/i18n.ts +59 -0
  275. package/src/index.ts +7 -0
  276. package/src/module.ts +61 -11
  277. package/src/services/AbstractSocket.ts +3 -0
  278. package/src/services/actionInput.spec.ts +155 -0
  279. package/src/services/actionInput.ts +120 -0
  280. package/src/services/interactions.spec.ts +175 -0
  281. package/src/services/interactions.ts +722 -0
  282. package/src/services/keyboardControls.ts +2 -1
  283. package/src/services/loadMap.ts +5 -1
  284. package/src/services/mmorpg-connection.spec.ts +99 -0
  285. package/src/services/mmorpg-connection.ts +69 -0
  286. package/src/services/mmorpg.ts +70 -36
  287. package/src/services/pointerContext.spec.ts +36 -0
  288. package/src/services/pointerContext.ts +84 -0
  289. package/src/services/save.spec.ts +127 -0
  290. package/src/services/standalone-message.ts +7 -0
  291. package/src/services/standalone.spec.ts +54 -0
  292. package/src/services/standalone.ts +44 -12
  293. package/src/utils/getEntityProp.spec.ts +96 -0
  294. package/src/utils/getEntityProp.ts +4 -3
  295. package/src/utils/mapId.ts +2 -0
  296. package/src/utils/readPropValue.ts +16 -0
  297. package/dist/node_modules/.pnpm/@signe_di@2.9.0/node_modules/@signe/di/dist/index.js.map +0 -1
  298. package/dist/node_modules/.pnpm/@signe_reactive@2.8.3/node_modules/@signe/reactive/dist/index.js +0 -457
  299. package/dist/node_modules/.pnpm/@signe_reactive@2.8.3/node_modules/@signe/reactive/dist/index.js.map +0 -1
  300. package/dist/node_modules/.pnpm/@signe_reactive@2.9.0/node_modules/@signe/reactive/dist/index.js +0 -463
  301. package/dist/node_modules/.pnpm/@signe_reactive@2.9.0/node_modules/@signe/reactive/dist/index.js.map +0 -1
  302. package/dist/node_modules/.pnpm/@signe_room@2.9.0/node_modules/@signe/room/dist/index.js +0 -2191
  303. package/dist/node_modules/.pnpm/@signe_room@2.9.0/node_modules/@signe/room/dist/index.js.map +0 -1
  304. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/chunk-7QVYU63E.js +0 -10
  305. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/chunk-7QVYU63E.js.map +0 -1
  306. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/client/index.js +0 -91
  307. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/client/index.js.map +0 -1
  308. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/index.js.map +0 -1
  309. package/dist/node_modules/.pnpm/dset@3.1.4/node_modules/dset/dist/index.js +0 -14
  310. package/dist/node_modules/.pnpm/dset@3.1.4/node_modules/dset/dist/index.js.map +0 -1
  311. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-HAC622V3.js.map +0 -1
  312. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-S74YV6PU.js.map +0 -1
  313. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/index.js +0 -2
  314. package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js.map +0 -1
@@ -1,45 +1,125 @@
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);
34
109
  this.mapShakeTrigger = trigger();
35
110
  this.controlsReady = signal(void 0);
36
111
  this.gamePause = signal(false);
37
112
  this.predictionEnabled = false;
38
113
  this.SERVER_CORRECTION_THRESHOLD = 30;
114
+ this.localMovementAuthority = false;
115
+ this.lastLocalMovementInputAt = 0;
116
+ this.LOCAL_MOVEMENT_AUTHORITY_ACK_GRACE_MS = 250;
39
117
  this.inputFrameCounter = 0;
40
118
  this.pendingPredictionFrames = [];
41
119
  this.lastClientPhysicsStepAt = 0;
42
120
  this.frameOffset = 0;
121
+ this.latestServerTickAt = 0;
122
+ this.dashLockedUntil = 0;
43
123
  this.rtt = 0;
44
124
  this.pingInterval = null;
45
125
  this.PING_INTERVAL_MS = 5e3;
@@ -53,12 +133,20 @@ var RpgClientEngine = class {
53
133
  this.playersReceived$ = new BehaviorSubject(false);
54
134
  this.eventsReceived$ = new BehaviorSubject(false);
55
135
  this.sceneResetQueued = false;
136
+ this.mapTransitionInProgress = false;
137
+ this.socketListenersInitialized = false;
138
+ this.clientReadyForMapChanges = false;
139
+ this.pendingMapChanges = [];
56
140
  this.tickSubscriptions = [];
141
+ this.pendingSyncPackets = [];
57
142
  this.notificationManager = new NotificationManager();
58
143
  this.webSocket = inject(WebSocketToken);
59
144
  this.guiService = inject(RpgGui);
60
145
  this.loadMapService = inject(LoadMapToken);
61
146
  this.hooks = inject(ModulesToken);
147
+ this.i18nService = getOrCreateI18nService(context);
148
+ this.i18nService.addMessages(RpgClientBuiltinI18n, "rpgjs-client", 0);
149
+ this.projectiles = new ProjectileManager(this.hooks, (projectile) => this.predictProjectileImpact(projectile));
62
150
  this.globalConfig = inject(GlobalConfigToken);
63
151
  if (!this.globalConfig) this.globalConfig = {};
64
152
  if (!this.globalConfig.box) this.globalConfig.box = {
@@ -72,9 +160,38 @@ var RpgClientEngine = class {
72
160
  id: "animation",
73
161
  component: PrebuiltComponentAnimations.Animation
74
162
  });
163
+ this.registerSpriteComponent("rpg:text", __ce_component$2);
164
+ this.registerSpriteComponent("rpg:hpBar", __ce_component$3);
165
+ this.registerSpriteComponent("rpg:spBar", __ce_component$3);
166
+ this.registerSpriteComponent("rpg:bar", __ce_component$3);
167
+ this.registerSpriteComponent("rpg:shape", __ce_component$4);
168
+ this.registerSpriteComponent("rpg:image", __ce_component$5);
75
169
  this.predictionEnabled = this.globalConfig?.prediction?.enabled !== false;
170
+ this.localMovementAuthority = this.resolveLocalMovementAuthority();
76
171
  this.initializePredictionController();
77
172
  }
173
+ resolveLocalMovementAuthority() {
174
+ const predictionConfig = this.globalConfig?.prediction;
175
+ const configured = this.globalConfig?.movementAuthority ?? predictionConfig?.movementAuthority ?? predictionConfig?.authority ?? predictionConfig?.mode;
176
+ if (configured === "server" || configured === "network" || configured === false) return false;
177
+ if (configured === "client" || configured === "local" || configured === true) return true;
178
+ return this.webSocket.mode === "standalone";
179
+ }
180
+ setLocale(locale) {
181
+ this.locale = locale;
182
+ }
183
+ getLocale() {
184
+ return this.locale || this.i18nService.defaultLocale;
185
+ }
186
+ t(key, params) {
187
+ return this.i18nService.t(key, params, this.getLocale());
188
+ }
189
+ i18n() {
190
+ return {
191
+ locale: this.getLocale(),
192
+ t: (key, params) => this.t(key, params)
193
+ };
194
+ }
78
195
  /**
79
196
  * Assigns a CanvasEngine KeyboardControls instance to the dependency injection context
80
197
  *
@@ -120,12 +237,25 @@ var RpgClientEngine = class {
120
237
  this.sceneMap = new RpgClientMap();
121
238
  this.sceneMap.configureClientPrediction(this.predictionEnabled);
122
239
  this.sceneMap.loadPhysic();
240
+ this.resolveSceneMapComponent();
241
+ inject(SaveClientService).initialize();
242
+ this.initListeners();
243
+ this.guiService._initialize();
244
+ try {
245
+ await this.webSocket.connection();
246
+ } catch (error) {
247
+ this.stopPingPong();
248
+ await this.callConnectError(error);
249
+ throw error;
250
+ }
123
251
  this.selector = document.body.querySelector("#rpg");
124
252
  const bootstrapOptions = this.globalConfig?.bootstrapCanvasOptions;
125
- const { app, canvasElement } = await bootstrapCanvas(this.selector, component, bootstrapOptions);
253
+ const { app, canvasElement } = await bootstrapCanvas(this.selector, __ce_component, bootstrapOptions);
254
+ this.installCanvasResizeGuard(app);
126
255
  this.canvasApp = app;
127
256
  this.canvasElement = canvasElement;
128
257
  this.renderer = app.renderer;
258
+ this.setupPointerTracking();
129
259
  this.tick = canvasElement?.propObservables?.context["tick"].observable;
130
260
  const inputCheckSubscription = this.tick.subscribe(() => {
131
261
  if (Date.now() - this.lastInputTime > 100) {
@@ -137,6 +267,7 @@ var RpgClientEngine = class {
137
267
  this.tickSubscriptions.push(inputCheckSubscription);
138
268
  this.hooks.callHooks("client-spritesheets-load", this).subscribe();
139
269
  this.hooks.callHooks("client-spritesheetResolver-load", this).subscribe();
270
+ this.flushPendingSyncPackets();
140
271
  this.hooks.callHooks("client-sounds-load", this).subscribe();
141
272
  this.hooks.callHooks("client-soundResolver-load", this).subscribe();
142
273
  RpgSound.init(this);
@@ -144,14 +275,20 @@ var RpgClientEngine = class {
144
275
  this.hooks.callHooks("client-gui-load", this).subscribe();
145
276
  this.hooks.callHooks("client-particles-load", this).subscribe();
146
277
  this.hooks.callHooks("client-componentAnimations-load", this).subscribe();
278
+ this.hooks.callHooks("client-clientVisuals-load", this).subscribe();
279
+ this.hooks.callHooks("client-projectiles-load", this).subscribe();
280
+ this.hooks.callHooks("client-interactions-load", this).subscribe();
147
281
  this.hooks.callHooks("client-sprite-load", this).subscribe();
148
282
  await lastValueFrom(this.hooks.callHooks("client-engine-onStart", this));
283
+ this.clientReadyForMapChanges = true;
284
+ this.flushPendingMapChanges();
149
285
  this.resizeHandler = () => {
150
286
  this.hooks.callHooks("client-engine-onWindowResize", this).subscribe();
151
287
  };
152
288
  window.addEventListener("resize", this.resizeHandler);
153
289
  const tickSubscription = this.tick.subscribe((tick) => {
154
290
  this.stepClientPhysicsTick();
291
+ this.projectiles.step();
155
292
  this.flushPendingPredictedStates();
156
293
  this.flushPendingMovePath();
157
294
  this.hooks.callHooks("client-engine-onStep", this, tick).subscribe();
@@ -162,12 +299,133 @@ var RpgClientEngine = class {
162
299
  }
163
300
  });
164
301
  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
- });
302
+ this.startPingPong();
303
+ }
304
+ installCanvasResizeGuard(app) {
305
+ if (!app || typeof app.resize !== "function") return;
306
+ const originalResize = app.resize.bind(app);
307
+ app.resize = () => {
308
+ const targetSize = this.readCanvasResizeTargetSize(app);
309
+ const rendererSize = this.readCanvasRendererSize(app);
310
+ if (targetSize && rendererSize && targetSize.width === rendererSize.width && targetSize.height === rendererSize.height) {
311
+ this.cancelCanvasResizeFrame(app);
312
+ return;
313
+ }
314
+ originalResize();
315
+ };
316
+ }
317
+ readCanvasResizeTargetSize(app) {
318
+ const resizeTarget = app?.resizeTo;
319
+ if (!resizeTarget || typeof window === "undefined") return null;
320
+ const rawWidth = resizeTarget === window ? window.innerWidth : resizeTarget.clientWidth;
321
+ const rawHeight = resizeTarget === window ? window.innerHeight : resizeTarget.clientHeight;
322
+ const width = Math.round(Number(rawWidth));
323
+ const height = Math.round(Number(rawHeight));
324
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width < 0 || height < 0) return null;
325
+ return {
326
+ width,
327
+ height
328
+ };
329
+ }
330
+ readCanvasRendererSize(app) {
331
+ const screen = app?.renderer?.screen;
332
+ const width = Math.round(Number(screen?.width));
333
+ const height = Math.round(Number(screen?.height));
334
+ if (!Number.isFinite(width) || !Number.isFinite(height)) return null;
335
+ return {
336
+ width,
337
+ height
338
+ };
339
+ }
340
+ cancelCanvasResizeFrame(app) {
341
+ if (typeof app?._cancelResize === "function") app._cancelResize();
342
+ }
343
+ resolveSceneMapComponent() {
344
+ const components = this.hooks.getHookFunctions("client-sceneMap-component");
345
+ const component = components[components.length - 1];
346
+ if (component) this.sceneMapComponent = component;
347
+ }
348
+ setupPointerTracking() {
349
+ const renderer = this.renderer;
350
+ const canvas = renderer?.canvas ?? renderer?.view ?? this.canvasApp?.canvas;
351
+ if (!canvas || typeof canvas.addEventListener !== "function") return;
352
+ this.pointerCanvas = canvas;
353
+ const updatePointer = (event) => {
354
+ const rect = canvas.getBoundingClientRect();
355
+ const screen = {
356
+ x: event.clientX - rect.left,
357
+ y: event.clientY - rect.top
358
+ };
359
+ const viewport = this.findViewportInstance();
360
+ let world = screen;
361
+ if (viewport && typeof viewport.toWorld === "function") {
362
+ const point = viewport.toWorld(screen.x, screen.y);
363
+ world = {
364
+ x: Number(point.x),
365
+ y: Number(point.y)
366
+ };
367
+ } else if (viewport && typeof viewport.toLocal === "function") {
368
+ const point = viewport.toLocal(screen);
369
+ world = {
370
+ x: Number(point.x),
371
+ y: Number(point.y)
372
+ };
373
+ }
374
+ this.pointer.update(screen, world);
375
+ };
376
+ this.pointerMoveHandler = (event) => {
377
+ updatePointer(event);
378
+ this.interactions.handlePointerMove(event);
379
+ };
380
+ this.pointerUpHandler = (event) => {
381
+ updatePointer(event);
382
+ this.interactions.handlePointerUp(event);
383
+ };
384
+ this.pointerCancelHandler = (event) => {
385
+ updatePointer(event);
386
+ this.interactions.cancelDrag(event);
387
+ };
388
+ canvas.addEventListener("pointermove", this.pointerMoveHandler);
389
+ canvas.addEventListener("pointerdown", this.pointerMoveHandler);
390
+ canvas.addEventListener("pointerup", this.pointerUpHandler);
391
+ canvas.addEventListener("pointercancel", this.pointerCancelHandler);
392
+ canvas.addEventListener("pointerleave", this.pointerCancelHandler);
393
+ }
394
+ updatePointerFromInteractionEvent(event) {
395
+ const global = event?.global ?? event?.data?.global;
396
+ if (!global) {
397
+ this.pointer.updateFromEvent(event);
398
+ return;
399
+ }
400
+ const screen = {
401
+ x: Number(global.x),
402
+ y: Number(global.y)
403
+ };
404
+ if (!Number.isFinite(screen.x) || !Number.isFinite(screen.y)) {
405
+ this.pointer.updateFromEvent(event);
406
+ return;
407
+ }
408
+ const viewport = this.findViewportInstance();
409
+ if (viewport && typeof viewport.toWorld === "function") {
410
+ const point = viewport.toWorld(screen.x, screen.y);
411
+ this.pointer.update(screen, {
412
+ x: Number(point.x),
413
+ y: Number(point.y)
414
+ });
415
+ return;
416
+ }
417
+ this.pointer.update(screen);
418
+ }
419
+ findViewportInstance() {
420
+ const find = (node) => {
421
+ if (!node) return void 0;
422
+ if (typeof node?.toWorld === "function" || node?.constructor?.name === "Viewport") return node;
423
+ for (const child of node.children ?? []) {
424
+ const viewport = find(child);
425
+ if (viewport) return viewport;
426
+ }
427
+ };
428
+ return find(this.canvasApp?.stage);
171
429
  }
172
430
  prepareSyncPayload(data) {
173
431
  const payload = { ...data ?? {} };
@@ -175,7 +433,8 @@ var RpgClientEngine = class {
175
433
  delete payload.timestamp;
176
434
  const myId = this.playerIdSignal();
177
435
  const players = payload.players;
178
- if (this.predictionEnabled && !!this.prediction?.hasPendingInputs() && myId && players && players[myId]) {
436
+ const localPatch = myId && players ? players[myId] : void 0;
437
+ if (this.shouldPreserveLocalPlayerPosition(localPatch) && myId && players && players[myId]) {
179
438
  const localPatch = { ...players[myId] };
180
439
  delete localPatch.x;
181
440
  delete localPatch.y;
@@ -188,6 +447,18 @@ var RpgClientEngine = class {
188
447
  }
189
448
  return payload;
190
449
  }
450
+ shouldPreserveLocalPlayerPosition(localPatch) {
451
+ if (!localPatch) return false;
452
+ if (this.predictionEnabled && !!this.prediction?.hasPendingInputs()) return true;
453
+ return this.shouldKeepLocalPlayerMovement();
454
+ }
455
+ shouldKeepLocalPlayerMovement() {
456
+ if (!this.localMovementAuthority || this.mapTransitionInProgress) return false;
457
+ const myId = this.playerIdSignal();
458
+ if (!(myId ? this.sceneMap?.players?.()?.[myId] : void 0)) return false;
459
+ if (this.prediction?.hasPendingInputs()) return true;
460
+ return Date.now() - this.lastLocalMovementInputAt <= this.LOCAL_MOVEMENT_AUTHORITY_ACK_GRACE_MS;
461
+ }
191
462
  normalizeAckWithSyncState(ack, syncData) {
192
463
  const myId = this.playerIdSignal();
193
464
  if (!myId) return ack;
@@ -201,43 +472,30 @@ var RpgClientEngine = class {
201
472
  };
202
473
  }
203
474
  initListeners() {
475
+ if (this.socketListenersInitialized) return;
476
+ this.socketListenersInitialized = true;
204
477
  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];
478
+ if (!this.tick) {
479
+ this.pendingSyncPackets.push(data);
480
+ return;
224
481
  }
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);
482
+ this.applySyncPacket(data);
228
483
  });
229
484
  this.webSocket.on("pong", (data) => {
230
- this.rtt = Date.now() - data.clientTime;
485
+ const now = Date.now();
486
+ this.rtt = now - data.clientTime;
231
487
  const estimatedTicksInFlight = Math.floor(this.rtt / 2 / (1e3 / 60));
232
488
  const estimatedServerTickNow = data.serverTick + estimatedTicksInFlight;
489
+ this.updateServerTickEstimate(estimatedServerTickNow, now);
233
490
  if (this.inputFrameCounter > 0) this.frameOffset = estimatedServerTickNow - data.clientFrame;
234
491
  console.debug(`[Ping/Pong] RTT: ${this.rtt}ms, ServerTick: ${data.serverTick}, FrameOffset: ${this.frameOffset}`);
235
492
  });
236
493
  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);
494
+ if (!this.clientReadyForMapChanges) {
495
+ this.pendingMapChanges.push(data);
496
+ return;
497
+ }
498
+ this.handleChangeMap(data);
241
499
  });
242
500
  this.webSocket.on("showComponentAnimation", (data) => {
243
501
  const { params, object, position, id } = data;
@@ -245,14 +503,42 @@ var RpgClientEngine = class {
245
503
  const player = object ? this.sceneMap.getObjectById(object) : void 0;
246
504
  this.getComponentAnimation(id).displayEffect(params, player || position);
247
505
  });
506
+ this.webSocket.on("clientVisual", (data) => {
507
+ this.playClientVisual(data);
508
+ });
509
+ this.webSocket.on("projectile:spawnBatch", (data) => {
510
+ if (!this.shouldProcessProjectilePacket(data)) return;
511
+ this.projectiles.spawnBatch(data?.projectiles ?? [], {
512
+ mapId: data?.mapId,
513
+ currentServerTick: this.estimateServerTick(),
514
+ tickDurationMs: this.getPhysicsTickDurationMs()
515
+ });
516
+ });
517
+ this.webSocket.on("projectile:impactBatch", (data) => {
518
+ if (!this.shouldProcessProjectilePacket(data)) return;
519
+ this.projectiles.impactBatch(data?.impacts ?? [], { mapId: data?.mapId });
520
+ });
521
+ this.webSocket.on("projectile:destroyBatch", (data) => {
522
+ if (!this.shouldProcessProjectilePacket(data)) return;
523
+ this.projectiles.destroyBatch(data?.projectiles ?? [], { mapId: data?.mapId });
524
+ });
525
+ this.webSocket.on("projectile:clear", (data) => {
526
+ if (!this.shouldProcessProjectilePacket(data)) return;
527
+ this.projectiles.clear();
528
+ });
248
529
  this.webSocket.on("notification", (data) => {
249
530
  this.notificationManager.add(data);
250
531
  });
251
532
  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);
533
+ const { animationName, nbTimes, object, graphic, restoreAnimationName, restoreGraphics } = data;
534
+ const player = object ? this.sceneMap.getObjectById(object) : void 0;
535
+ if (!player) return;
536
+ const restoreOptions = {
537
+ restoreAnimationName,
538
+ restoreGraphics
539
+ };
540
+ if (graphic !== void 0) player.setAnimation(animationName, graphic, nbTimes, restoreOptions);
541
+ else player.setAnimation(animationName, nbTimes, restoreOptions);
256
542
  });
257
543
  this.webSocket.on("playSound", (data) => {
258
544
  const { soundId, volume, loop } = data;
@@ -314,6 +600,10 @@ var RpgClientEngine = class {
314
600
  seed: raw.seed
315
601
  });
316
602
  });
603
+ this.webSocket.on("lightingState", (data) => {
604
+ const raw = data && typeof data === "object" && "value" in data ? data.value : data;
605
+ this.sceneMap.lightingState.set(normalizeLightingState(raw));
606
+ });
317
607
  this.webSocket.on("open", () => {
318
608
  this.hooks.callHooks("client-engine-onConnected", this, this.socket).subscribe();
319
609
  this.startPingPong();
@@ -323,9 +613,82 @@ var RpgClientEngine = class {
323
613
  this.stopPingPong();
324
614
  });
325
615
  this.webSocket.on("error", (error) => {
326
- this.hooks.callHooks("client-engine-onConnectError", this, error, this.socket).subscribe();
616
+ this.callConnectError(error);
617
+ });
618
+ }
619
+ beginMapTransfer(nextMapId) {
620
+ this.mapTransitionInProgress = true;
621
+ this.currentMapRoomId = nextMapId;
622
+ this.sceneResetQueued = false;
623
+ this.clearClientPredictionStates();
624
+ this.sceneMap.weatherState.set(null);
625
+ this.sceneMap.lightingState.set(null);
626
+ this.sceneMap.clearLightSpots();
627
+ this.clearComponentAnimations();
628
+ this.projectiles.setMapId(nextMapId);
629
+ this.cameraFollowTargetId.set(null);
630
+ this.sceneMap.reset();
631
+ this.sceneMap.loadPhysic();
632
+ }
633
+ clearComponentAnimations() {
634
+ this.componentAnimations.forEach((componentAnimation) => {
635
+ componentAnimation.instance?.clear?.();
327
636
  });
328
637
  }
638
+ shouldProcessProjectilePacket(data) {
639
+ if (this.mapTransitionInProgress) return false;
640
+ const packetMapId = normalizeRoomMapId(typeof data?.mapId === "string" ? data.mapId : void 0);
641
+ const currentMapId = normalizeRoomMapId(this.currentMapRoomId);
642
+ return !packetMapId || !currentMapId || packetMapId === currentMapId;
643
+ }
644
+ async callConnectError(error) {
645
+ await lastValueFrom(this.hooks.callHooks("client-engine-onConnectError", this, error, this.socket));
646
+ }
647
+ flushPendingSyncPackets() {
648
+ const packets = this.pendingSyncPackets;
649
+ this.pendingSyncPackets = [];
650
+ packets.forEach((packet) => this.applySyncPacket(packet));
651
+ }
652
+ flushPendingMapChanges() {
653
+ const packets = this.pendingMapChanges;
654
+ this.pendingMapChanges = [];
655
+ packets.forEach((packet) => this.handleChangeMap(packet));
656
+ }
657
+ handleChangeMap(data) {
658
+ const nextMapId = typeof data?.mapId === "string" ? data.mapId : void 0;
659
+ this.beginMapTransfer(nextMapId);
660
+ const transferToken = typeof data?.transferToken === "string" ? data.transferToken : void 0;
661
+ this.loadScene(data.mapId, transferToken);
662
+ }
663
+ applySyncPacket(data) {
664
+ if (data.pId) {
665
+ this.playerIdSignal.set(data.pId);
666
+ this.playerIdReceived$.next(true);
667
+ }
668
+ if (this.sceneResetQueued) {
669
+ const weatherState = this.sceneMap.weatherState();
670
+ const lightingState = this.sceneMap.lightingState();
671
+ this.sceneMap.reset();
672
+ this.sceneMap.weatherState.set(weatherState);
673
+ this.sceneMap.lightingState.set(lightingState);
674
+ this.sceneMap.loadPhysic();
675
+ this.sceneResetQueued = false;
676
+ }
677
+ this.hooks.callHooks("client-sceneMap-onChanges", this.sceneMap, { partial: data }).subscribe();
678
+ const ack = data?.ack;
679
+ const normalizedAck = ack && typeof ack.frame === "number" ? this.normalizeAckWithSyncState(ack, data) : void 0;
680
+ const payload = this.prepareSyncPayload(data);
681
+ load(this.sceneMap, payload, true);
682
+ if (normalizedAck) this.applyServerAck(normalizedAck);
683
+ for (const playerId in payload.players ?? {}) {
684
+ const player = payload.players[playerId];
685
+ if (!player._param) continue;
686
+ for (const param in player._param) this.sceneMap.players()[playerId]._param()[param] = player._param[param];
687
+ }
688
+ const players = payload.players || this.sceneMap.players();
689
+ if (players && Object.keys(players).length > 0) this.playersReceived$.next(true);
690
+ if ((payload.events || this.sceneMap.events()) !== void 0) this.eventsReceived$.next(true);
691
+ }
329
692
  /**
330
693
  * Start periodic ping/pong for client-server synchronization
331
694
  *
@@ -402,18 +765,25 @@ var RpgClientEngine = class {
402
765
  room: mapId,
403
766
  query: transferToken ? { transferToken } : void 0
404
767
  });
405
- await this.webSocket.reconnect(() => {
406
- inject(SaveClientService).initialize(this.webSocket);
407
- this.initListeners();
408
- this.guiService._initialize();
409
- });
768
+ try {
769
+ await this.webSocket.reconnect();
770
+ } catch (error) {
771
+ this.mapTransitionInProgress = false;
772
+ this.stopPingPong();
773
+ await this.callConnectError(error);
774
+ throw error;
775
+ }
410
776
  const res = await this.loadMapService.load(mapId);
777
+ const loadedLighting = typeof res?.lighting !== "undefined" ? res.lighting : res?.data?.lighting;
778
+ if (typeof loadedLighting !== "undefined") this.sceneMap.lightingState.set(normalizeLightingState(loadedLighting));
411
779
  this.sceneMap.data.set(res);
412
780
  if (this.playerIdSignal()) this.playerIdReceived$.next(true);
413
781
  const players = this.sceneMap.players();
414
782
  if (players && Object.keys(players).length > 0) this.playersReceived$.next(true);
415
783
  if (this.sceneMap.events() !== void 0) this.eventsReceived$.next(true);
416
784
  this.mapLoadCompleted$.next(true);
785
+ this.currentMapRoomId = mapId;
786
+ this.mapTransitionInProgress = false;
417
787
  this.sceneMap.configureClientPrediction(this.predictionEnabled);
418
788
  this.sceneMap.loadPhysic();
419
789
  }
@@ -458,7 +828,7 @@ var RpgClientEngine = class {
458
828
  * If not found and a resolver is set, it calls the resolver to create the spritesheet.
459
829
  * The resolved spritesheet is automatically cached for future use.
460
830
  *
461
- * @param id - The spritesheet ID to retrieve
831
+ * @param id - The spritesheet ID or legacy tile ID to retrieve
462
832
  * @returns The spritesheet if found or created, or undefined if not found and no resolver
463
833
  * @returns Promise<any> if the resolver is asynchronous
464
834
  *
@@ -474,12 +844,20 @@ var RpgClientEngine = class {
474
844
  getSpriteSheet(id) {
475
845
  if (this.spritesheets.has(id)) return this.spritesheets.get(id);
476
846
  if (this.spritesheetResolver) {
847
+ if (this.spritesheetPromises.has(id)) return this.spritesheetPromises.get(id);
477
848
  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 {
849
+ if (result instanceof Promise) {
850
+ const promise = result.then((spritesheet) => {
851
+ if (spritesheet) this.spritesheets.set(id, spritesheet);
852
+ this.spritesheetPromises.delete(id);
853
+ return spritesheet;
854
+ }).catch((error) => {
855
+ this.spritesheetPromises.delete(id);
856
+ throw error;
857
+ });
858
+ this.spritesheetPromises.set(id, promise);
859
+ return promise;
860
+ } else {
483
861
  if (result) this.spritesheets.set(id, result);
484
862
  return result;
485
863
  }
@@ -807,6 +1185,95 @@ var RpgClientEngine = class {
807
1185
  return component;
808
1186
  }
809
1187
  /**
1188
+ * Register a reusable sprite component that can be addressed by the server.
1189
+ *
1190
+ * Server-side component definitions only carry the component id and
1191
+ * serializable props. The client registry maps that id to the CanvasEngine
1192
+ * component that performs the actual rendering.
1193
+ *
1194
+ * @param id - Stable component id used by server component definitions
1195
+ * @param component - CanvasEngine component to render for this id
1196
+ * @returns The registered component
1197
+ *
1198
+ * @example
1199
+ * ```ts
1200
+ * engine.registerSpriteComponent('guildBadge', GuildBadgeComponent);
1201
+ * ```
1202
+ */
1203
+ registerSpriteComponent(id, component) {
1204
+ this.spriteComponents.set(id, component);
1205
+ return component;
1206
+ }
1207
+ /**
1208
+ * Get a reusable sprite component by id.
1209
+ *
1210
+ * @param id - Component id registered on the client
1211
+ * @returns The CanvasEngine component, or undefined when missing
1212
+ */
1213
+ getSpriteComponent(id) {
1214
+ return this.spriteComponents.get(id);
1215
+ }
1216
+ /**
1217
+ * Register a custom event component resolver.
1218
+ *
1219
+ * The last resolver returning a component wins. This lets later modules
1220
+ * override earlier defaults without replacing the whole map scene.
1221
+ *
1222
+ * @param resolver - Function receiving the synced event object
1223
+ * @returns The registered resolver
1224
+ */
1225
+ addEventComponentResolver(resolver) {
1226
+ return this.eventComponentResolvers.add(resolver);
1227
+ }
1228
+ /**
1229
+ * Resolve the custom CanvasEngine component for an event, if any.
1230
+ *
1231
+ * @param event - Synced client event object
1232
+ * @returns The component/config returned by the last matching resolver
1233
+ */
1234
+ resolveEventComponent(event) {
1235
+ return this.eventComponentResolvers.resolve(event);
1236
+ }
1237
+ registerProjectileComponent(type, component) {
1238
+ return this.projectiles.register(type, component);
1239
+ }
1240
+ getProjectileComponent(type) {
1241
+ return this.projectiles.get(type);
1242
+ }
1243
+ /**
1244
+ * Register a named client visual macro.
1245
+ *
1246
+ * Client visuals are small client-side functions that group existing visual
1247
+ * primitives such as flash, sound, component animations, sprite animation, or
1248
+ * map shake. The server sends only the visual name and a serializable payload.
1249
+ *
1250
+ * @param name - Stable visual name sent by the server
1251
+ * @param handler - Client-side visual handler
1252
+ * @returns The registered handler
1253
+ */
1254
+ registerClientVisual(name, handler) {
1255
+ return this.clientVisuals.register(name, handler);
1256
+ }
1257
+ /**
1258
+ * Register several named client visual macros.
1259
+ *
1260
+ * @param visuals - Map of visual names to client-side handlers
1261
+ */
1262
+ registerClientVisuals(visuals) {
1263
+ this.clientVisuals.registerMany(visuals);
1264
+ }
1265
+ /**
1266
+ * Play a registered client visual locally.
1267
+ *
1268
+ * This is also used by the websocket listener when the server calls
1269
+ * `player.clientVisual()` or `map.clientVisual()`.
1270
+ *
1271
+ * @param packet - Visual name and serializable payload
1272
+ */
1273
+ playClientVisual(packet) {
1274
+ return this.clientVisuals.play(packet, this);
1275
+ }
1276
+ /**
810
1277
  * Add a component animation to the engine
811
1278
  *
812
1279
  * Component animations are temporary visual effects that can be displayed
@@ -882,18 +1349,47 @@ var RpgClientEngine = class {
882
1349
  * duration: 1000,
883
1350
  * onFinish: () => console.log('Fade complete')
884
1351
  * });
1352
+ *
1353
+ * // Wait until the transition component calls onFinish
1354
+ * await engine.startTransition('fade', { duration: 1000 });
885
1355
  * ```
886
1356
  */
887
1357
  startTransition(id, props = {}) {
888
1358
  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);
1359
+ return new Promise((resolve) => {
1360
+ let finished = false;
1361
+ const finish = (data) => {
1362
+ if (finished) return;
1363
+ finished = true;
1364
+ props?.onFinish?.(data);
1365
+ resolve();
1366
+ };
1367
+ this.guiService.display(id, {
1368
+ ...props,
1369
+ onFinish: finish
1370
+ });
1371
+ });
890
1372
  }
891
1373
  async processInput({ input }) {
1374
+ if (this.stopProcessingInput) return;
1375
+ const currentPlayer = this.sceneMap.getCurrentPlayer();
1376
+ if (!(!currentPlayer || getCanMoveValue(currentPlayer))) {
1377
+ this.interruptCurrentPlayerMovement(currentPlayer);
1378
+ return;
1379
+ }
892
1380
  const timestamp = Date.now();
1381
+ const movementInput = isDashInput(input) ? normalizeDashInput(input, currentPlayer?.direction?.()) : input;
1382
+ if (!movementInput) return;
1383
+ if (isDashInput(movementInput)) {
1384
+ const cooldown = movementInput.cooldown ?? DEFAULT_DASH_COOLDOWN_MS;
1385
+ if (timestamp < this.dashLockedUntil) return;
1386
+ this.dashLockedUntil = timestamp + cooldown;
1387
+ }
1388
+ this.lastLocalMovementInputAt = timestamp;
893
1389
  let frame;
894
1390
  let tick;
895
1391
  if (this.predictionEnabled && this.prediction) {
896
- const meta = this.prediction.recordInput(input, timestamp);
1392
+ const meta = this.prediction.recordInput(movementInput, timestamp);
897
1393
  frame = meta.frame;
898
1394
  tick = meta.tick;
899
1395
  } else {
@@ -902,29 +1398,38 @@ var RpgClientEngine = class {
902
1398
  }
903
1399
  this.inputFrameCounter = frame;
904
1400
  this.hooks.callHooks("client-engine-onInput", this, {
905
- input,
1401
+ input: movementInput,
906
1402
  playerId: this.playerId
907
1403
  }).subscribe();
908
- const currentPlayer = this.sceneMap.getCurrentPlayer();
909
1404
  const bodyReady = this.ensureCurrentPlayerBody();
910
1405
  if (currentPlayer && bodyReady) {
911
- currentPlayer.changeDirection(input);
912
- this.sceneMap.moveBody(currentPlayer, input);
1406
+ this.applyPredictedMovementInput(currentPlayer, movementInput);
913
1407
  if (this.predictionEnabled && this.prediction) {
914
1408
  this.pendingPredictionFrames.push(frame);
915
1409
  if (this.pendingPredictionFrames.length > 240) this.pendingPredictionFrames = this.pendingPredictionFrames.slice(-240);
916
1410
  }
917
1411
  }
918
- this.emitMovePacket(input, frame, tick, timestamp, true);
919
- this.lastInputTime = Date.now();
1412
+ this.emitMovePacket(movementInput, frame, tick, timestamp, true);
1413
+ this.lastInputTime = isDashInput(movementInput) ? Date.now() + (movementInput.duration ?? DEFAULT_DASH_DURATION_MS) : Date.now();
920
1414
  }
921
- processAction({ action }) {
1415
+ async processDash(input = {}) {
1416
+ const currentPlayer = this.sceneMap.getCurrentPlayer();
1417
+ const dashInput = normalizeDashInput(input, typeof currentPlayer?.direction === "function" ? currentPlayer.direction() : currentPlayer?.direction);
1418
+ if (!dashInput) return;
1419
+ await this.processInput({ input: dashInput });
1420
+ }
1421
+ processAction(action, data) {
922
1422
  if (this.stopProcessingInput) return;
1423
+ const currentPlayer = this.sceneMap.getCurrentPlayer();
1424
+ if (!(!currentPlayer || getCanMoveValue(currentPlayer))) return;
1425
+ const payload = normalizeActionInput(action, data);
923
1426
  this.hooks.callHooks("client-engine-onInput", this, {
924
- input: "action",
1427
+ input: payload.action,
1428
+ action: payload.action,
1429
+ data: payload.data,
925
1430
  playerId: this.playerId
926
1431
  }).subscribe();
927
- this.webSocket.emit("action", { action });
1432
+ this.webSocket.emit("action", payload);
928
1433
  }
929
1434
  get PIXI() {
930
1435
  return PIXI;
@@ -938,9 +1443,43 @@ var RpgClientEngine = class {
938
1443
  get scene() {
939
1444
  return this.sceneMap;
940
1445
  }
1446
+ getObjectById(id) {
1447
+ return this.sceneMap?.getObjectById(id);
1448
+ }
941
1449
  getPhysicsTick() {
942
1450
  return this.sceneMap?.getTick?.() ?? 0;
943
1451
  }
1452
+ getPhysicsTickDurationMs() {
1453
+ const timeStep = this.sceneMap?.physic?.getWorld?.()?.getTimeStep?.();
1454
+ return typeof timeStep === "number" && Number.isFinite(timeStep) && timeStep > 0 ? timeStep * 1e3 : 1e3 / 60;
1455
+ }
1456
+ updateServerTickEstimate(serverTick, now = Date.now()) {
1457
+ if (typeof serverTick !== "number" || !Number.isFinite(serverTick)) return;
1458
+ this.latestServerTick = serverTick;
1459
+ this.latestServerTickAt = now;
1460
+ }
1461
+ estimateServerTick(now = Date.now()) {
1462
+ if (typeof this.latestServerTick !== "number" || this.latestServerTickAt <= 0) return;
1463
+ const elapsedTicks = Math.max(0, (now - this.latestServerTickAt) / this.getPhysicsTickDurationMs());
1464
+ return this.latestServerTick + elapsedTicks;
1465
+ }
1466
+ predictProjectileImpact(projectile) {
1467
+ if (projectile.predictImpact === false) return null;
1468
+ const sceneMap = this.sceneMap;
1469
+ if (!sceneMap?.physic || !Number.isFinite(projectile.range) || projectile.range <= 0) return null;
1470
+ const origin = projectile.origin;
1471
+ const direction = projectile.direction;
1472
+ 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;
1473
+ 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);
1474
+ if (!hit) return null;
1475
+ return {
1476
+ id: projectile.id,
1477
+ targetId: hit.entity.uuid,
1478
+ x: hit.point.x,
1479
+ y: hit.point.y,
1480
+ distance: hit.distance
1481
+ };
1482
+ }
944
1483
  ensureCurrentPlayerBody() {
945
1484
  const player = this.sceneMap?.getCurrentPlayer();
946
1485
  const myId = this.playerIdSignal();
@@ -986,7 +1525,7 @@ var RpgClientEngine = class {
986
1525
  input: entry.direction,
987
1526
  x: state.x,
988
1527
  y: state.y,
989
- direction: state.direction ?? entry.direction
1528
+ direction: state.direction ?? resolveMoveDirection(entry.direction)
990
1529
  });
991
1530
  }
992
1531
  if (trajectory.length > this.MAX_MOVE_TRAJECTORY_POINTS) return trajectory.slice(-this.MAX_MOVE_TRAJECTORY_POINTS);
@@ -1008,6 +1547,11 @@ var RpgClientEngine = class {
1008
1547
  }
1009
1548
  flushPendingMovePath() {
1010
1549
  if (!this.predictionEnabled || !this.prediction) return;
1550
+ const player = this.sceneMap?.getCurrentPlayer?.();
1551
+ if (player && !getCanMoveValue(player)) {
1552
+ this.interruptCurrentPlayerMovement(player);
1553
+ return;
1554
+ }
1011
1555
  const pendingInputs = this.prediction.getPendingInputs();
1012
1556
  if (pendingInputs.length === 0) return;
1013
1557
  const latest = pendingInputs[pendingInputs.length - 1];
@@ -1016,6 +1560,17 @@ var RpgClientEngine = class {
1016
1560
  if (now - this.lastMovePathSentAt < this.MOVE_PATH_RESEND_INTERVAL_MS) return;
1017
1561
  this.emitMovePacket(latest.direction, latest.frame, latest.tick, now, false);
1018
1562
  }
1563
+ applyPredictedMovementInput(player, input) {
1564
+ if (isDashInput(input)) {
1565
+ const direction = vectorToDirection(input.direction);
1566
+ player.changeDirection(direction);
1567
+ return Boolean(this.sceneMap.dashBody?.(player, input));
1568
+ }
1569
+ const direction = resolveMoveDirection(input);
1570
+ if (!direction) return false;
1571
+ player.changeDirection(direction);
1572
+ return Boolean(this.sceneMap.moveBody?.(player, direction));
1573
+ }
1019
1574
  getLocalPlayerState() {
1020
1575
  const currentPlayer = this.sceneMap?.getCurrentPlayer();
1021
1576
  if (!currentPlayer) return {
@@ -1124,6 +1679,31 @@ var RpgClientEngine = class {
1124
1679
  this.lastMovePathSentFrame = 0;
1125
1680
  }
1126
1681
  /**
1682
+ * Stop local movement immediately and discard pending predicted movement.
1683
+ *
1684
+ * Use this before a blocking action such as an A-RPG attack, dialog, dash
1685
+ * startup, or any client-side state where already buffered movement inputs
1686
+ * must not be replayed after server reconciliation.
1687
+ *
1688
+ * @param player - Player object to stop. Defaults to the current player.
1689
+ * @returns `true` when a player was found and interrupted.
1690
+ *
1691
+ * @example
1692
+ * ```ts
1693
+ * engine.interruptCurrentPlayerMovement();
1694
+ * ```
1695
+ */
1696
+ interruptCurrentPlayerMovement(player = this.sceneMap?.getCurrentPlayer?.()) {
1697
+ if (!player) return false;
1698
+ this.sceneMap?.stopMovement?.(player);
1699
+ this.prediction?.clearPendingInputs();
1700
+ this.pendingPredictionFrames = [];
1701
+ this.lastInputTime = 0;
1702
+ this.lastMovePathSentAt = Date.now();
1703
+ this.lastMovePathSentFrame = this.inputFrameCounter;
1704
+ return true;
1705
+ }
1706
+ /**
1127
1707
  * Trigger a flash animation on a sprite
1128
1708
  *
1129
1709
  * This method allows you to trigger a flash effect on any sprite from client-side code.
@@ -1177,11 +1757,13 @@ var RpgClientEngine = class {
1177
1757
  if (sprite && typeof sprite.flash === "function") sprite.flash(options);
1178
1758
  }
1179
1759
  applyServerAck(ack) {
1760
+ this.updateServerTickEstimate(ack.serverTick);
1761
+ const keepLocalMovement = this.shouldKeepLocalPlayerMovement();
1180
1762
  if (this.predictionEnabled && this.prediction) {
1181
1763
  const result = this.prediction.applyServerAck({
1182
1764
  frame: ack.frame,
1183
1765
  serverTick: ack.serverTick,
1184
- state: typeof ack.x === "number" && typeof ack.y === "number" ? {
1766
+ state: !keepLocalMovement && typeof ack.x === "number" && typeof ack.y === "number" ? {
1185
1767
  x: ack.x,
1186
1768
  y: ack.y,
1187
1769
  direction: ack.direction
@@ -1191,6 +1773,7 @@ var RpgClientEngine = class {
1191
1773
  return;
1192
1774
  }
1193
1775
  if (typeof ack.x !== "number" || typeof ack.y !== "number") return;
1776
+ if (keepLocalMovement) return;
1194
1777
  const player = this.getCurrentPlayer();
1195
1778
  const myId = this.playerIdSignal();
1196
1779
  if (!player || !myId) return;
@@ -1205,13 +1788,17 @@ var RpgClientEngine = class {
1205
1788
  reconcilePrediction(authoritativeState, pendingInputs) {
1206
1789
  const player = this.getCurrentPlayer();
1207
1790
  if (!player) return;
1791
+ if (!getCanMoveValue(player)) {
1792
+ this.interruptCurrentPlayerMovement(player);
1793
+ return;
1794
+ }
1208
1795
  this.sceneMap.stopMovement(player);
1209
1796
  this.applyAuthoritativeState(authoritativeState);
1210
1797
  if (!pendingInputs.length) return;
1211
1798
  const replayInputs = pendingInputs.slice(-600);
1212
1799
  for (const entry of replayInputs) {
1213
1800
  if (!entry?.direction) continue;
1214
- this.sceneMap.moveBody(player, entry.direction);
1801
+ this.applyPredictedMovementInput(player, entry.direction);
1215
1802
  this.sceneMap.stepPredictionTick();
1216
1803
  this.prediction?.attachPredictedState(entry.frame, this.getLocalPlayerState());
1217
1804
  }
@@ -1277,6 +1864,19 @@ var RpgClientEngine = class {
1277
1864
  window.removeEventListener("resize", this.resizeHandler);
1278
1865
  this.resizeHandler = void 0;
1279
1866
  }
1867
+ if (this.pointerMoveHandler && this.pointerCanvas) {
1868
+ this.pointerCanvas.removeEventListener("pointermove", this.pointerMoveHandler);
1869
+ this.pointerCanvas.removeEventListener("pointerdown", this.pointerMoveHandler);
1870
+ if (this.pointerUpHandler) this.pointerCanvas.removeEventListener("pointerup", this.pointerUpHandler);
1871
+ if (this.pointerCancelHandler) {
1872
+ this.pointerCanvas.removeEventListener("pointercancel", this.pointerCancelHandler);
1873
+ this.pointerCanvas.removeEventListener("pointerleave", this.pointerCancelHandler);
1874
+ }
1875
+ this.pointerMoveHandler = void 0;
1876
+ this.pointerUpHandler = void 0;
1877
+ this.pointerCancelHandler = void 0;
1878
+ this.pointerCanvas = void 0;
1879
+ }
1280
1880
  const rendererStillExists = this.renderer && typeof this.renderer.destroy === "function";
1281
1881
  if (this.canvasApp && typeof this.canvasApp.destroy === "function") {
1282
1882
  try {
@@ -1308,6 +1908,7 @@ var RpgClientEngine = class {
1308
1908
  this.cameraFollowTargetId.set(null);
1309
1909
  this.spriteComponentsBehind.set([]);
1310
1910
  this.spriteComponentsInFront.set([]);
1911
+ this.eventComponentResolvers.clear();
1311
1912
  this.spritesheets.clear();
1312
1913
  this.sounds.clear();
1313
1914
  this.componentAnimations = [];