@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
@@ -5,6 +5,7 @@ export enum Control {
5
5
  Attack = 'attack',
6
6
  Defense = 'defense',
7
7
  Skill = 'skill',
8
+ Dash = 'dash',
8
9
  Back = 'back',
9
10
  Up = 1,
10
11
  Down = 3,
@@ -17,4 +18,4 @@ export function provideKeyboardControls() {
17
18
  provide: 'KeyboardControls',
18
19
  useValue: null,
19
20
  };
20
- }
21
+ }
@@ -1,5 +1,5 @@
1
1
  import { Context, inject } from "@signe/di";
2
- import { UpdateMapToken, UpdateMapService } from "@rpgjs/common";
2
+ import { UpdateMapToken, UpdateMapService, type LightingState } from "@rpgjs/common";
3
3
 
4
4
  export const LoadMapToken = 'LoadMapToken'
5
5
 
@@ -20,8 +20,12 @@ type MapData = {
20
20
  height?: number;
21
21
  /** Optional map events data (NPCs, interactive objects, etc.) */
22
22
  events?: any;
23
+ /** Optional named positions, for example Tiled point objects used by changeMap("map", "name") */
24
+ positions?: Record<string, { x: number; y: number; z?: number }>;
23
25
  /** Optional map identifier, defaults to the mapId parameter if not provided */
24
26
  id?: string;
27
+ /** Optional initial lighting state for the loaded map */
28
+ lighting?: LightingState | null;
25
29
  }
26
30
 
27
31
  /**
@@ -0,0 +1,99 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { isNativeSocketEvent, parseSocketMessage, waitForRpgjsConnected } from "./mmorpg-connection";
3
+
4
+ function wait(ms = 0): Promise<void> {
5
+ return new Promise((resolve) => setTimeout(resolve, ms));
6
+ }
7
+
8
+ class FakeConnection extends EventTarget {
9
+ serverMessage(data: any) {
10
+ this.dispatchEvent(new MessageEvent("message", {
11
+ data: typeof data === "string" ? data : JSON.stringify(data),
12
+ }));
13
+ }
14
+
15
+ close(reason = "", code = 1008) {
16
+ this.dispatchEvent(new CloseEvent("close", {
17
+ code,
18
+ reason,
19
+ }));
20
+ }
21
+
22
+ fail(message = "WebSocket connection failed") {
23
+ this.dispatchEvent(new ErrorEvent("error", {
24
+ message,
25
+ }));
26
+ }
27
+ }
28
+
29
+ describe("MMORPG connection gate", () => {
30
+ test("resolves only after the RPGJS connected packet", async () => {
31
+ const conn = new FakeConnection();
32
+ let resolved = false;
33
+ const promise = waitForRpgjsConnected(conn).then(() => {
34
+ resolved = true;
35
+ });
36
+
37
+ conn.serverMessage({ type: "sync", value: { pId: "player-1" } });
38
+ await wait();
39
+
40
+ expect(resolved).toBe(false);
41
+
42
+ conn.serverMessage({ type: "connected", value: { id: "conn-1" } });
43
+ await promise;
44
+
45
+ expect(resolved).toBe(true);
46
+ });
47
+
48
+ test("rejects when the socket closes before the RPGJS connected packet", async () => {
49
+ const conn = new FakeConnection();
50
+ const promise = waitForRpgjsConnected(conn);
51
+
52
+ conn.close("Authentication failed");
53
+
54
+ await expect(promise).rejects.toThrow("Authentication failed");
55
+ });
56
+
57
+ test("can ignore the clean close emitted by PartySocket during manual reconnect", async () => {
58
+ const conn = new FakeConnection();
59
+ const promise = waitForRpgjsConnected(conn, 10000, { ignoreCleanClose: true });
60
+
61
+ conn.close("", 1000);
62
+ await wait();
63
+
64
+ conn.serverMessage({ type: "connected", value: { id: "conn-2" } });
65
+
66
+ await expect(promise).resolves.toBeUndefined();
67
+ });
68
+
69
+ test("can ignore PartySocket reconnect close events with non-string reasons", async () => {
70
+ const conn = new FakeConnection();
71
+ const promise = waitForRpgjsConnected(conn, 10000, { ignoreCleanClose: true });
72
+
73
+ conn.dispatchEvent(new CloseEvent("close", {
74
+ code: 1000,
75
+ reason: new Event("close") as any,
76
+ }));
77
+ await wait();
78
+
79
+ conn.serverMessage({ type: "connected", value: { id: "conn-3" } });
80
+
81
+ await expect(promise).resolves.toBeUndefined();
82
+ });
83
+
84
+ test("rejects when the socket errors before the RPGJS connected packet", async () => {
85
+ const conn = new FakeConnection();
86
+ const promise = waitForRpgjsConnected(conn);
87
+
88
+ conn.fail("Unauthorized");
89
+
90
+ await expect(promise).rejects.toThrow("Unauthorized");
91
+ });
92
+
93
+ test("parses connection messages and detects native socket events", () => {
94
+ expect(parseSocketMessage(JSON.stringify({ type: "connected" }))).toEqual({ type: "connected" });
95
+ expect(parseSocketMessage("not-json")).toBeUndefined();
96
+ expect(isNativeSocketEvent("open")).toBe(true);
97
+ expect(isNativeSocketEvent("sync")).toBe(false);
98
+ });
99
+ });
@@ -0,0 +1,69 @@
1
+ export function parseSocketMessage(data: any) {
2
+ if (typeof data !== "string") {
3
+ return data;
4
+ }
5
+ try {
6
+ return JSON.parse(data);
7
+ }
8
+ catch {
9
+ return undefined;
10
+ }
11
+ }
12
+
13
+ export function isNativeSocketEvent(event: string) {
14
+ return event === "open" || event === "close" || event === "error";
15
+ }
16
+
17
+ export function waitForRpgjsConnected(
18
+ conn: any,
19
+ timeoutMs = 10000,
20
+ options: { ignoreCleanClose?: boolean } = {},
21
+ ): Promise<void> {
22
+ return new Promise((resolve, reject) => {
23
+ let timeoutId: number | undefined;
24
+
25
+ const cleanup = () => {
26
+ conn.removeEventListener("message", onMessage);
27
+ conn.removeEventListener("close", onClose);
28
+ conn.removeEventListener("error", onError);
29
+ if (timeoutId !== undefined) {
30
+ window.clearTimeout(timeoutId);
31
+ }
32
+ };
33
+ const rejectWith = (error: Error) => {
34
+ cleanup();
35
+ reject(error);
36
+ };
37
+ const onMessage = (event: MessageEvent) => {
38
+ const data = parseSocketMessage(event.data);
39
+ if (data?.type !== "connected") {
40
+ return;
41
+ }
42
+ cleanup();
43
+ resolve();
44
+ };
45
+ const onClose = (event: CloseEvent) => {
46
+ const rawReason: unknown = (event as any).reason;
47
+ if (
48
+ options.ignoreCleanClose
49
+ && (event.code === 1000 || rawReason instanceof Event || typeof rawReason !== "string")
50
+ ) {
51
+ return;
52
+ }
53
+ const reason = typeof rawReason === "string" && rawReason
54
+ ? rawReason
55
+ : "WebSocket closed before RPGJS connection was accepted";
56
+ rejectWith(new Error(reason));
57
+ };
58
+ const onError = (event: ErrorEvent) => {
59
+ rejectWith(new Error(event.message || "WebSocket connection failed"));
60
+ };
61
+
62
+ conn.addEventListener("message", onMessage);
63
+ conn.addEventListener("close", onClose);
64
+ conn.addEventListener("error", onError);
65
+ timeoutId = window.setTimeout(() => {
66
+ rejectWith(new Error("RPGJS connection timeout"));
67
+ }, timeoutMs);
68
+ });
69
+ }
@@ -2,21 +2,27 @@ import { Context } from "@signe/di";
2
2
  import { connectionRoom } from "@signe/sync/client";
3
3
  import { RpgGui } from "../Gui/Gui";
4
4
  import { RpgClientEngine } from "../RpgClientEngine";
5
- import { AbstractWebsocket, SocketUpdateProperties, WebSocketToken } from "./AbstractSocket";
5
+ import { AbstractWebsocket, SocketQuery, SocketUpdateProperties, WebSocketToken } from "./AbstractSocket";
6
6
  import { UpdateMapService, UpdateMapToken } from "@rpgjs/common";
7
7
  import { provideKeyboardControls } from "./keyboardControls";
8
8
  import { provideSaveClient } from "./save";
9
+ import { isNativeSocketEvent, waitForRpgjsConnected } from "./mmorpg-connection";
9
10
 
10
- interface MmorpgOptions {
11
+ export interface MmorpgOptions {
11
12
  host?: string;
12
13
  connectionId?: string;
13
14
  connectionIdScope?: "local" | "session" | "ephemeral";
15
+ query?: SocketQuery | (() => SocketQuery | undefined);
16
+ socketOptions?: Record<string, any>;
14
17
  }
15
18
 
16
- class BridgeWebsocket extends AbstractWebsocket {
19
+ export class BridgeWebsocket extends AbstractWebsocket {
20
+ readonly mode = "mmorpg" as const;
21
+
17
22
  private socket: any;
18
23
  private privateId: string;
19
24
  private pendingOn: Array<{ event: string; callback: (data: any) => void }> = [];
25
+ private acceptedOpenListeners = new Set<(data: any) => void>();
20
26
  private targetRoom = "lobby-1";
21
27
 
22
28
  constructor(protected context: Context, private options: MmorpgOptions = {}) {
@@ -51,6 +57,14 @@ class BridgeWebsocket extends AbstractWebsocket {
51
57
  return id;
52
58
  }
53
59
 
60
+ private resolveQuery(): SocketQuery {
61
+ const query = typeof this.options.query === "function"
62
+ ? this.options.query()
63
+ : this.options.query;
64
+
65
+ return query ?? {};
66
+ }
67
+
54
68
  async connection(listeners?: (data: any) => void) {
55
69
  // tmp
56
70
  class Room {
@@ -59,14 +73,28 @@ class BridgeWebsocket extends AbstractWebsocket {
59
73
  const instance = new Room()
60
74
  const host = this.options.host || window.location.host;
61
75
  this.socket = await connectionRoom({
76
+ maxRetries: 0,
77
+ ...this.options.socketOptions,
62
78
  host,
63
79
  room: this.targetRoom,
64
- id: this.privateId
80
+ id: this.privateId,
81
+ query: {
82
+ ...this.resolveQuery(),
83
+ id: this.privateId,
84
+ },
65
85
  }, instance)
66
86
 
67
- listeners?.(this.socket)
68
- this.pendingOn.forEach(({ event, callback }) => this.socket.on(event, callback));
87
+ const pendingOn = this.pendingOn;
69
88
  this.pendingOn = [];
89
+ pendingOn
90
+ .filter(({ event }) => !this.isNativeSocketEvent(event))
91
+ .forEach(({ event, callback }) => this.attachEvent(event, callback));
92
+ await waitForRpgjsConnected(this.socket.conn);
93
+ pendingOn
94
+ .filter(({ event }) => this.isNativeSocketEvent(event))
95
+ .forEach(({ event, callback }) => this.attachEvent(event, callback));
96
+ this.emitAcceptedOpen();
97
+ listeners?.(this.socket)
70
98
  }
71
99
 
72
100
  on(key: string, callback: (data: any) => void) {
@@ -74,11 +102,19 @@ class BridgeWebsocket extends AbstractWebsocket {
74
102
  this.pendingOn.push({ event: key, callback });
75
103
  return;
76
104
  }
77
- this.socket.on(key, callback);
105
+ this.attachEvent(key, callback);
78
106
  }
79
107
 
80
108
  off(event: string, callback: (data: any) => void) {
81
109
  if (!this.socket) return;
110
+ if (event === "open") {
111
+ this.acceptedOpenListeners.delete(callback);
112
+ return;
113
+ }
114
+ if (this.isNativeSocketEvent(event)) {
115
+ this.socket.conn.removeEventListener(event, callback);
116
+ return;
117
+ }
82
118
  this.socket.off(event, callback);
83
119
  }
84
120
 
@@ -86,6 +122,23 @@ class BridgeWebsocket extends AbstractWebsocket {
86
122
  this.socket.emit(event, data);
87
123
  }
88
124
 
125
+ private attachEvent(event: string, callback: (data: any) => void) {
126
+ if (event === "open") {
127
+ this.acceptedOpenListeners.add(callback);
128
+ return;
129
+ }
130
+ if (this.isNativeSocketEvent(event)) {
131
+ this.socket.conn.addEventListener(event, callback);
132
+ return;
133
+ }
134
+ this.socket.on(event, callback);
135
+ }
136
+
137
+ private emitAcceptedOpen() {
138
+ const event = new Event("open");
139
+ this.acceptedOpenListeners.forEach((callback) => callback(event));
140
+ }
141
+
89
142
  updateProperties({ room, host, query }: SocketUpdateProperties) {
90
143
  if (!this.socket?.conn) return;
91
144
  this.targetRoom = room;
@@ -93,44 +146,25 @@ class BridgeWebsocket extends AbstractWebsocket {
93
146
  room,
94
147
  id: this.privateId,
95
148
  host: host || this.options.host || window.location.host,
96
- query,
149
+ query: {
150
+ ...this.resolveQuery(),
151
+ ...query,
152
+ id: this.privateId,
153
+ },
97
154
  })
98
155
  }
99
156
 
100
- private waitForNextOpen(conn: any, timeoutMs = 10000): Promise<void> {
101
- return new Promise((resolve, reject) => {
102
- let timeoutId: number | undefined;
103
- const onOpen = () => {
104
- cleanup();
105
- resolve();
106
- };
107
- const onError = () => {
108
- cleanup();
109
- reject(new Error("WebSocket reconnect failed"));
110
- };
111
- const cleanup = () => {
112
- conn.removeEventListener("open", onOpen);
113
- conn.removeEventListener("error", onError);
114
- if (timeoutId !== undefined) {
115
- window.clearTimeout(timeoutId);
116
- }
117
- };
118
-
119
- conn.addEventListener("open", onOpen);
120
- conn.addEventListener("error", onError);
121
- timeoutId = window.setTimeout(() => {
122
- cleanup();
123
- reject(new Error("WebSocket reconnect timeout"));
124
- }, timeoutMs);
125
- });
157
+ private isNativeSocketEvent(event: string) {
158
+ return isNativeSocketEvent(event);
126
159
  }
127
160
 
128
161
  async reconnect(_listeners?: (data: any) => void): Promise<void> {
129
162
  if (!this.socket?.conn) return;
130
163
  const conn = this.socket.conn;
131
- const opened = this.waitForNextOpen(conn);
164
+ const connected = waitForRpgjsConnected(conn, 10000, { ignoreCleanClose: true });
132
165
  conn.reconnect();
133
- await opened;
166
+ await connected;
167
+ this.emitAcceptedOpen();
134
168
  }
135
169
 
136
170
  getCurrentRoom(): string {
@@ -0,0 +1,36 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { createClientPointerContext } from "./pointerContext";
3
+
4
+ describe("createClientPointerContext", () => {
5
+ test("returns null before any pointer event", () => {
6
+ const pointer = createClientPointerContext();
7
+
8
+ expect(pointer.screen()).toBeNull();
9
+ expect(pointer.world()).toBeNull();
10
+ });
11
+
12
+ test("stores screen and world coordinates from pointer events", () => {
13
+ const pointer = createClientPointerContext();
14
+
15
+ const world = pointer.updateFromEvent({
16
+ global: { x: 120, y: 80 },
17
+ currentTarget: {
18
+ toLocal(point: { x: number; y: number }) {
19
+ return { x: point.x + 10, y: point.y + 20 };
20
+ },
21
+ },
22
+ });
23
+
24
+ expect(world).toEqual({ x: 130, y: 100 });
25
+ expect(pointer.screen()).toEqual({ x: 120, y: 80 });
26
+ expect(pointer.world()).toEqual({ x: 130, y: 100 });
27
+ });
28
+
29
+ test("ignores pointer events without usable coordinates", () => {
30
+ const pointer = createClientPointerContext();
31
+
32
+ expect(pointer.updateFromEvent({ global: { x: Number.NaN, y: 80 } })).toBeNull();
33
+ expect(pointer.screen()).toBeNull();
34
+ expect(pointer.world()).toBeNull();
35
+ });
36
+ });
@@ -0,0 +1,84 @@
1
+ export interface ClientPointerPosition {
2
+ x: number;
3
+ y: number;
4
+ }
5
+
6
+ export interface ClientPointerContext {
7
+ screen(): ClientPointerPosition | null;
8
+ world(): ClientPointerPosition | null;
9
+ update(screen: ClientPointerPosition | null, world?: ClientPointerPosition | null): ClientPointerPosition | null;
10
+ updateFromEvent(event: any): ClientPointerPosition | null;
11
+ }
12
+
13
+ function toPosition(point: any): ClientPointerPosition | null {
14
+ const x = Number(point?.x);
15
+ const y = Number(point?.y);
16
+
17
+ if (!Number.isFinite(x) || !Number.isFinite(y)) {
18
+ return null;
19
+ }
20
+
21
+ return { x, y };
22
+ }
23
+
24
+ function extractGlobalPoint(event: any): ClientPointerPosition | null {
25
+ return toPosition(event?.global)
26
+ ?? toPosition(event?.data?.global)
27
+ ?? toPosition(event);
28
+ }
29
+
30
+ function extractWorldPoint(event: any, global: ClientPointerPosition | null): ClientPointerPosition | null {
31
+ const target = event?.currentTarget ?? event?.target;
32
+
33
+ if (target && global && typeof target.toLocal === "function") {
34
+ return toPosition(target.toLocal(global));
35
+ }
36
+
37
+ if (typeof event?.getLocalPosition === "function") {
38
+ return toPosition(event.getLocalPosition(target));
39
+ }
40
+
41
+ if (typeof event?.data?.getLocalPosition === "function") {
42
+ return toPosition(event.data.getLocalPosition(target));
43
+ }
44
+
45
+ return global;
46
+ }
47
+
48
+ export function createClientPointerContext(): ClientPointerContext {
49
+ let lastScreen: ClientPointerPosition | null = null;
50
+ let lastWorld: ClientPointerPosition | null = null;
51
+
52
+ const update = (screen: ClientPointerPosition | null, world?: ClientPointerPosition | null) => {
53
+ const nextScreen = toPosition(screen);
54
+ const nextWorld = toPosition(world) ?? nextScreen;
55
+
56
+ if (nextScreen) {
57
+ lastScreen = nextScreen;
58
+ }
59
+
60
+ if (nextWorld) {
61
+ lastWorld = nextWorld;
62
+ }
63
+
64
+ return lastWorld ? { ...lastWorld } : null;
65
+ };
66
+
67
+ return {
68
+ screen() {
69
+ return lastScreen ? { ...lastScreen } : null;
70
+ },
71
+
72
+ world() {
73
+ return lastWorld ? { ...lastWorld } : null;
74
+ },
75
+
76
+ update,
77
+
78
+ updateFromEvent(event: any) {
79
+ const global = extractGlobalPoint(event);
80
+ const world = extractWorldPoint(event, global);
81
+ return update(global, world);
82
+ },
83
+ };
84
+ }
@@ -0,0 +1,127 @@
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
2
+ import { SaveClientService, provideSaveClient } from "./save";
3
+
4
+ const createSocket = () => {
5
+ const listeners = new Map<string, (data: any) => void>();
6
+ const emitted: Array<{ event: string; data: any }> = [];
7
+
8
+ return {
9
+ listeners,
10
+ emitted,
11
+ on: vi.fn((event: string, callback: (data: any) => void) => {
12
+ listeners.set(event, callback);
13
+ }),
14
+ off: vi.fn((event: string) => {
15
+ listeners.delete(event);
16
+ }),
17
+ emit: vi.fn((event: string, data: any) => {
18
+ emitted.push({ event, data });
19
+ }),
20
+ };
21
+ };
22
+
23
+ const createService = (socket = createSocket()) => {
24
+ const service = Object.create(SaveClientService.prototype) as SaveClientService;
25
+ (service as any).webSocket = socket;
26
+ (service as any).pending = new Map();
27
+ (service as any).requestCounter = 0;
28
+ return { service, socket };
29
+ };
30
+
31
+ describe("SaveClientService", () => {
32
+ beforeEach(() => {
33
+ vi.useFakeTimers();
34
+ vi.setSystemTime(new Date("2026-01-02T03:04:05.000Z"));
35
+ });
36
+
37
+ afterEach(() => {
38
+ vi.useRealTimers();
39
+ });
40
+
41
+ test("registers save result listeners idempotently", () => {
42
+ const { service, socket } = createService();
43
+
44
+ service.initialize();
45
+
46
+ expect(socket.off).toHaveBeenCalledTimes(4);
47
+ expect(socket.on).toHaveBeenCalledTimes(4);
48
+ expect([...socket.listeners.keys()]).toEqual([
49
+ "save.list.result",
50
+ "save.save.result",
51
+ "save.load.result",
52
+ "save.error",
53
+ ]);
54
+ });
55
+
56
+ test("lists, saves and loads slots through request/response events", async () => {
57
+ const { service, socket } = createService();
58
+ service.initialize();
59
+
60
+ const listPromise = service.listSlots();
61
+ expect(socket.emitted[0]).toEqual({
62
+ event: "save.list",
63
+ data: { requestId: "1767323045000-1" },
64
+ });
65
+ socket.listeners.get("save.list.result")?.({
66
+ requestId: "1767323045000-1",
67
+ slots: [{ id: "slot-a" }],
68
+ });
69
+ await expect(listPromise).resolves.toEqual([{ id: "slot-a" }]);
70
+
71
+ const savePromise = service.saveSlot(2, { map: "forest" } as any);
72
+ expect(socket.emitted[1]).toEqual({
73
+ event: "save.save",
74
+ data: {
75
+ requestId: "1767323045000-2",
76
+ index: 2,
77
+ meta: { map: "forest" },
78
+ },
79
+ });
80
+ socket.listeners.get("save.save.result")?.({
81
+ requestId: "1767323045000-2",
82
+ index: 2,
83
+ slots: [null, null, { id: "slot-c" }],
84
+ });
85
+ await expect(savePromise).resolves.toEqual([null, null, { id: "slot-c" }]);
86
+
87
+ const loadPromise = service.loadSlot(2);
88
+ expect(socket.emitted[2]).toEqual({
89
+ event: "save.load",
90
+ data: { requestId: "1767323045000-3", index: 2 },
91
+ });
92
+ socket.listeners.get("save.load.result")?.({
93
+ requestId: "1767323045000-3",
94
+ index: 2,
95
+ ok: true,
96
+ });
97
+ await expect(loadPromise).resolves.toBe(true);
98
+ });
99
+
100
+ test("rejects matching pending requests on save errors and ignores stale responses", async () => {
101
+ const { service, socket } = createService();
102
+ service.initialize();
103
+
104
+ const listPromise = service.listSlots();
105
+
106
+ socket.listeners.get("save.list.result")?.({
107
+ requestId: "unknown",
108
+ slots: [{ id: "stale" }],
109
+ });
110
+ expect((service as any).pending.size).toBe(1);
111
+
112
+ socket.listeners.get("save.error")?.({
113
+ requestId: "1767323045000-1",
114
+ message: "Cannot save",
115
+ });
116
+
117
+ await expect(listPromise).rejects.toThrow("Cannot save");
118
+ expect((service as any).pending.size).toBe(0);
119
+ });
120
+
121
+ test("provides the client save service class", () => {
122
+ expect(provideSaveClient()).toEqual({
123
+ provide: SaveClientService,
124
+ useClass: SaveClientService,
125
+ });
126
+ });
127
+ });
@@ -0,0 +1,7 @@
1
+ export function normalizeStandaloneMessage(event: unknown): any {
2
+ const raw = event && typeof event === "object" && "data" in event
3
+ ? (event as MessageEvent).data
4
+ : event;
5
+
6
+ return typeof raw === "string" ? JSON.parse(raw) : raw;
7
+ }
@@ -0,0 +1,54 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { describe, expect, test, vi } from "vitest";
4
+ import { Context } from "@signe/di";
5
+ import { WebSocketToken } from "./AbstractSocket";
6
+ import { provideMmorpg } from "./mmorpg";
7
+ import { provideRpg } from "./standalone";
8
+ import { normalizeStandaloneMessage } from "./standalone-message";
9
+
10
+ describe("standalone websocket bridge", () => {
11
+ test("dispatches mock room object broadcasts to named listeners", () => {
12
+ const onSpawn = vi.fn();
13
+ const object = normalizeStandaloneMessage({
14
+ type: "projectile:spawnBatch",
15
+ value: {
16
+ projectiles: [{ id: "p1", type: "bolt" }],
17
+ },
18
+ });
19
+
20
+ if (object.type === "projectile:spawnBatch") {
21
+ onSpawn(object.value);
22
+ }
23
+
24
+ expect(onSpawn).toHaveBeenCalledWith({
25
+ projectiles: [{ id: "p1", type: "bolt" }],
26
+ });
27
+ });
28
+
29
+ test("still accepts browser-style string messages", () => {
30
+ expect(normalizeStandaloneMessage({
31
+ data: JSON.stringify({
32
+ type: "projectile:spawnBatch",
33
+ value: { projectiles: [] },
34
+ }),
35
+ })).toEqual({
36
+ type: "projectile:spawnBatch",
37
+ value: { projectiles: [] },
38
+ });
39
+ });
40
+
41
+ test("marks standalone and MMORPG websocket providers with their runtime mode", () => {
42
+ class Server {}
43
+ const context = new Context();
44
+ const standaloneProvider = provideRpg(Server).find(
45
+ (provider: any) => provider.provide === WebSocketToken,
46
+ ) as any;
47
+ const mmorpgProvider = provideMmorpg({ connectionId: "test-client" }).find(
48
+ (provider: any) => provider.provide === WebSocketToken,
49
+ ) as any;
50
+
51
+ expect(standaloneProvider.useFactory(context).mode).toBe("standalone");
52
+ expect(mmorpgProvider.useFactory(context).mode).toBe("mmorpg");
53
+ });
54
+ });