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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/LICENSE +19 -0
  3. package/dist/Game/AnimationManager.d.ts +1 -1
  4. package/dist/Game/AnimationManager.js +18 -9
  5. package/dist/Game/AnimationManager.js.map +1 -1
  6. package/dist/Game/AnimationManager.spec.d.ts +1 -0
  7. package/dist/Game/Event.js.map +1 -1
  8. package/dist/Game/Map.d.ts +9 -1
  9. package/dist/Game/Map.js +63 -5
  10. package/dist/Game/Map.js.map +1 -1
  11. package/dist/Game/Object.d.ts +47 -15
  12. package/dist/Game/Object.js +82 -38
  13. package/dist/Game/Object.js.map +1 -1
  14. package/dist/Game/Player.js.map +1 -1
  15. package/dist/Game/ProjectileManager.d.ts +89 -0
  16. package/dist/Game/ProjectileManager.js +179 -0
  17. package/dist/Game/ProjectileManager.js.map +1 -0
  18. package/dist/Game/ProjectileManager.spec.d.ts +1 -0
  19. package/dist/Gui/Gui.d.ts +17 -4
  20. package/dist/Gui/Gui.js +78 -48
  21. package/dist/Gui/Gui.js.map +1 -1
  22. package/dist/Gui/Gui.spec.d.ts +1 -0
  23. package/dist/Gui/NotificationManager.js.map +1 -1
  24. package/dist/Resource.js +1 -1
  25. package/dist/Resource.js.map +1 -1
  26. package/dist/RpgClient.d.ts +110 -15
  27. package/dist/RpgClientEngine.d.ts +86 -10
  28. package/dist/RpgClientEngine.js +306 -49
  29. package/dist/RpgClientEngine.js.map +1 -1
  30. package/dist/Sound.js.map +1 -1
  31. package/dist/_virtual/{_@oxc-project_runtime@0.122.0 → _@oxc-project_runtime@0.130.0}/helpers/decorate.js +1 -1
  32. package/dist/_virtual/{_@oxc-project_runtime@0.122.0 → _@oxc-project_runtime@0.130.0}/helpers/decorateMetadata.js +1 -1
  33. package/dist/components/animations/animation.ce.js +4 -5
  34. package/dist/components/animations/animation.ce.js.map +1 -1
  35. package/dist/components/animations/hit.ce.js +19 -25
  36. package/dist/components/animations/hit.ce.js.map +1 -1
  37. package/dist/components/animations/index.js +4 -4
  38. package/dist/components/animations/index.js.map +1 -1
  39. package/dist/components/character.ce.js +422 -240
  40. package/dist/components/character.ce.js.map +1 -1
  41. package/dist/components/dynamics/bar.ce.js +97 -0
  42. package/dist/components/dynamics/bar.ce.js.map +1 -0
  43. package/dist/components/dynamics/image.ce.js +24 -0
  44. package/dist/components/dynamics/image.ce.js.map +1 -0
  45. package/dist/components/dynamics/parse-value.d.ts +3 -0
  46. package/dist/components/dynamics/parse-value.js +54 -35
  47. package/dist/components/dynamics/parse-value.js.map +1 -1
  48. package/dist/components/dynamics/parse-value.spec.d.ts +1 -0
  49. package/dist/components/dynamics/shape-utils.d.ts +16 -0
  50. package/dist/components/dynamics/shape-utils.js +73 -0
  51. package/dist/components/dynamics/shape-utils.js.map +1 -0
  52. package/dist/components/dynamics/shape-utils.spec.d.ts +1 -0
  53. package/dist/components/dynamics/shape.ce.js +84 -0
  54. package/dist/components/dynamics/shape.ce.js.map +1 -0
  55. package/dist/components/dynamics/text.ce.js +34 -56
  56. package/dist/components/dynamics/text.ce.js.map +1 -1
  57. package/dist/components/gui/box.ce.js +6 -8
  58. package/dist/components/gui/box.ce.js.map +1 -1
  59. package/dist/components/gui/dialogbox/index.ce.js +56 -62
  60. package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
  61. package/dist/components/gui/gameover.ce.js +42 -65
  62. package/dist/components/gui/gameover.ce.js.map +1 -1
  63. package/dist/components/gui/hud/hud.ce.js +21 -30
  64. package/dist/components/gui/hud/hud.ce.js.map +1 -1
  65. package/dist/components/gui/menu/equip-menu.ce.js +112 -165
  66. package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
  67. package/dist/components/gui/menu/exit-menu.ce.js +8 -6
  68. package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
  69. package/dist/components/gui/menu/items-menu.ce.js +52 -69
  70. package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
  71. package/dist/components/gui/menu/main-menu.ce.js +75 -92
  72. package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
  73. package/dist/components/gui/menu/options-menu.ce.js +5 -4
  74. package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
  75. package/dist/components/gui/menu/skills-menu.ce.js +12 -17
  76. package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
  77. package/dist/components/gui/mobile/index.js +2 -2
  78. package/dist/components/gui/mobile/index.js.map +1 -1
  79. package/dist/components/gui/mobile/mobile.ce.js +5 -4
  80. package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
  81. package/dist/components/gui/notification/notification.ce.js +22 -24
  82. package/dist/components/gui/notification/notification.ce.js.map +1 -1
  83. package/dist/components/gui/save-load.ce.js +72 -249
  84. package/dist/components/gui/save-load.ce.js.map +1 -1
  85. package/dist/components/gui/shop/shop.ce.js +90 -127
  86. package/dist/components/gui/shop/shop.ce.js.map +1 -1
  87. package/dist/components/gui/title-screen.ce.js +45 -70
  88. package/dist/components/gui/title-screen.ce.js.map +1 -1
  89. package/dist/components/index.d.ts +2 -1
  90. package/dist/components/index.js +1 -0
  91. package/dist/components/player-components-utils.d.ts +67 -0
  92. package/dist/components/player-components-utils.js +162 -0
  93. package/dist/components/player-components-utils.js.map +1 -0
  94. package/dist/components/player-components-utils.spec.d.ts +1 -0
  95. package/dist/components/player-components.ce.js +189 -0
  96. package/dist/components/player-components.ce.js.map +1 -0
  97. package/dist/components/prebuilt/hp-bar.ce.js +42 -44
  98. package/dist/components/prebuilt/hp-bar.ce.js.map +1 -1
  99. package/dist/components/prebuilt/light-halo.ce.js +36 -59
  100. package/dist/components/prebuilt/light-halo.ce.js.map +1 -1
  101. package/dist/components/scenes/canvas.ce.js +165 -21
  102. package/dist/components/scenes/canvas.ce.js.map +1 -1
  103. package/dist/components/scenes/draw-map.ce.js +25 -32
  104. package/dist/components/scenes/draw-map.ce.js.map +1 -1
  105. package/dist/components/scenes/event-layer.ce.js +9 -8
  106. package/dist/components/scenes/event-layer.ce.js.map +1 -1
  107. package/dist/core/inject.js +1 -1
  108. package/dist/core/inject.js.map +1 -1
  109. package/dist/core/setup.js +1 -1
  110. package/dist/core/setup.js.map +1 -1
  111. package/dist/decorators/spritesheet.d.ts +1 -0
  112. package/dist/decorators/spritesheet.js +11 -0
  113. package/dist/decorators/spritesheet.js.map +1 -0
  114. package/dist/index.d.ts +4 -0
  115. package/dist/index.js +26 -21
  116. package/dist/module.js +15 -1
  117. package/dist/module.js.map +1 -1
  118. package/dist/node_modules/.pnpm/{@signe_di@2.9.0 → @signe_di@3.0.1}/node_modules/@signe/di/dist/index.js +7 -117
  119. package/dist/node_modules/.pnpm/@signe_di@3.0.1/node_modules/@signe/di/dist/index.js.map +1 -0
  120. package/dist/node_modules/.pnpm/@signe_reactive@3.0.1/node_modules/@signe/reactive/dist/index.js +239 -0
  121. package/dist/node_modules/.pnpm/@signe_reactive@3.0.1/node_modules/@signe/reactive/dist/index.js.map +1 -0
  122. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/chunk-EUXUH3YW.js +13 -0
  123. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/chunk-EUXUH3YW.js.map +1 -0
  124. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/index.js +696 -0
  125. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/index.js.map +1 -0
  126. package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/client/index.js +44 -0
  127. package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/client/index.js.map +1 -0
  128. package/dist/node_modules/.pnpm/{@signe_sync@2.9.0 → @signe_sync@3.0.1}/node_modules/@signe/sync/dist/index.js +57 -141
  129. package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/index.js.map +1 -0
  130. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-HAC622V3.js.map +1 -1
  131. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-S74YV6PU.js.map +1 -1
  132. package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js +27 -27
  133. package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js.map +1 -1
  134. package/dist/presets/animation.js.map +1 -1
  135. package/dist/presets/faceset.js.map +1 -1
  136. package/dist/presets/icon.js.map +1 -1
  137. package/dist/presets/index.js.map +1 -1
  138. package/dist/presets/lpc.js.map +1 -1
  139. package/dist/presets/rmspritesheet.js.map +1 -1
  140. package/dist/services/AbstractSocket.js.map +1 -1
  141. package/dist/services/actionInput.d.ts +12 -0
  142. package/dist/services/actionInput.js +27 -0
  143. package/dist/services/actionInput.js.map +1 -0
  144. package/dist/services/actionInput.spec.d.ts +1 -0
  145. package/dist/services/keyboardControls.js.map +1 -1
  146. package/dist/services/loadMap.d.ts +6 -0
  147. package/dist/services/loadMap.js +1 -1
  148. package/dist/services/loadMap.js.map +1 -1
  149. package/dist/services/mmorpg-connection.d.ts +5 -0
  150. package/dist/services/mmorpg-connection.js +50 -0
  151. package/dist/services/mmorpg-connection.js.map +1 -0
  152. package/dist/services/mmorpg-connection.spec.d.ts +1 -0
  153. package/dist/services/mmorpg.d.ts +10 -4
  154. package/dist/services/mmorpg.js +56 -33
  155. package/dist/services/mmorpg.js.map +1 -1
  156. package/dist/services/pointerContext.d.ts +11 -0
  157. package/dist/services/pointerContext.js +48 -0
  158. package/dist/services/pointerContext.js.map +1 -0
  159. package/dist/services/pointerContext.spec.d.ts +1 -0
  160. package/dist/services/save.js.map +1 -1
  161. package/dist/services/save.spec.d.ts +1 -0
  162. package/dist/services/standalone-message.d.ts +1 -0
  163. package/dist/services/standalone-message.js +9 -0
  164. package/dist/services/standalone-message.js.map +1 -0
  165. package/dist/services/standalone.js +4 -3
  166. package/dist/services/standalone.js.map +1 -1
  167. package/dist/services/standalone.spec.d.ts +1 -0
  168. package/dist/utils/getEntityProp.js +4 -3
  169. package/dist/utils/getEntityProp.js.map +1 -1
  170. package/dist/utils/getEntityProp.spec.d.ts +1 -0
  171. package/dist/utils/readPropValue.d.ts +2 -0
  172. package/dist/utils/readPropValue.js +13 -0
  173. package/dist/utils/readPropValue.js.map +1 -0
  174. package/package.json +13 -14
  175. package/src/Game/AnimationManager.spec.ts +30 -0
  176. package/src/Game/AnimationManager.ts +22 -10
  177. package/src/Game/Map.ts +91 -2
  178. package/src/Game/Object.ts +148 -69
  179. package/src/Game/ProjectileManager.spec.ts +338 -0
  180. package/src/Game/ProjectileManager.ts +324 -0
  181. package/src/Gui/Gui.spec.ts +273 -0
  182. package/src/Gui/Gui.ts +105 -50
  183. package/src/Resource.ts +1 -2
  184. package/src/RpgClient.ts +125 -17
  185. package/src/RpgClientEngine.ts +457 -87
  186. package/src/components/character.ce +441 -32
  187. package/src/components/dynamics/bar.ce +88 -0
  188. package/src/components/dynamics/image.ce +21 -0
  189. package/src/components/dynamics/parse-value.spec.ts +83 -0
  190. package/src/components/dynamics/parse-value.ts +111 -37
  191. package/src/components/dynamics/shape-utils.spec.ts +46 -0
  192. package/src/components/dynamics/shape-utils.ts +61 -0
  193. package/src/components/dynamics/shape.ce +90 -0
  194. package/src/components/dynamics/text.ce +35 -149
  195. package/src/components/gui/dialogbox/index.ce +18 -8
  196. package/src/components/gui/gameover.ce +2 -1
  197. package/src/components/gui/menu/equip-menu.ce +2 -1
  198. package/src/components/gui/menu/exit-menu.ce +2 -1
  199. package/src/components/gui/menu/items-menu.ce +3 -2
  200. package/src/components/gui/menu/main-menu.ce +2 -1
  201. package/src/components/gui/save-load.ce +2 -1
  202. package/src/components/gui/shop/shop.ce +3 -2
  203. package/src/components/gui/title-screen.ce +2 -1
  204. package/src/components/index.ts +2 -1
  205. package/src/components/player-components-utils.spec.ts +109 -0
  206. package/src/components/player-components-utils.ts +205 -0
  207. package/src/components/player-components.ce +222 -0
  208. package/src/components/prebuilt/hp-bar.ce +4 -3
  209. package/src/components/prebuilt/light-halo.ce +2 -2
  210. package/src/components/scenes/canvas.ce +175 -8
  211. package/src/components/scenes/draw-map.ce +18 -17
  212. package/src/components/scenes/event-layer.ce +1 -2
  213. package/src/core/setup.ts +2 -2
  214. package/src/decorators/spritesheet.ts +8 -0
  215. package/src/index.ts +4 -0
  216. package/src/module.ts +18 -1
  217. package/src/services/actionInput.spec.ts +101 -0
  218. package/src/services/actionInput.ts +53 -0
  219. package/src/services/loadMap.ts +2 -0
  220. package/src/services/mmorpg-connection.spec.ts +99 -0
  221. package/src/services/mmorpg-connection.ts +69 -0
  222. package/src/services/mmorpg.ts +68 -36
  223. package/src/services/pointerContext.spec.ts +36 -0
  224. package/src/services/pointerContext.ts +84 -0
  225. package/src/services/save.spec.ts +127 -0
  226. package/src/services/standalone-message.ts +7 -0
  227. package/src/services/standalone.spec.ts +34 -0
  228. package/src/services/standalone.ts +3 -2
  229. package/src/utils/getEntityProp.spec.ts +96 -0
  230. package/src/utils/getEntityProp.ts +4 -3
  231. package/src/utils/readPropValue.ts +16 -0
  232. package/dist/node_modules/.pnpm/@signe_di@2.9.0/node_modules/@signe/di/dist/index.js.map +0 -1
  233. package/dist/node_modules/.pnpm/@signe_reactive@2.8.3/node_modules/@signe/reactive/dist/index.js +0 -457
  234. package/dist/node_modules/.pnpm/@signe_reactive@2.8.3/node_modules/@signe/reactive/dist/index.js.map +0 -1
  235. package/dist/node_modules/.pnpm/@signe_reactive@2.9.0/node_modules/@signe/reactive/dist/index.js +0 -463
  236. package/dist/node_modules/.pnpm/@signe_reactive@2.9.0/node_modules/@signe/reactive/dist/index.js.map +0 -1
  237. package/dist/node_modules/.pnpm/@signe_room@2.9.0/node_modules/@signe/room/dist/index.js +0 -2191
  238. package/dist/node_modules/.pnpm/@signe_room@2.9.0/node_modules/@signe/room/dist/index.js.map +0 -1
  239. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/chunk-7QVYU63E.js +0 -10
  240. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/chunk-7QVYU63E.js.map +0 -1
  241. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/client/index.js +0 -91
  242. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/client/index.js.map +0 -1
  243. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/index.js.map +0 -1
  244. package/dist/node_modules/.pnpm/dset@3.1.4/node_modules/dset/dist/index.js +0 -14
  245. package/dist/node_modules/.pnpm/dset@3.1.4/node_modules/dset/dist/index.js.map +0 -1
@@ -1,35 +1,47 @@
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.0.1/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 { ProjectileManager } from "./Game/ProjectileManager.js";
23
+ import { createClientPointerContext } from "./services/pointerContext.js";
15
24
  import { Howl, bootstrapCanvas, signal, trigger } from "canvasengine";
16
- import { Direction, ModulesToken, PredictionController } from "@rpgjs/common";
25
+ import { Direction, ModulesToken, PredictionController, Vector2, normalizeLightingState } from "@rpgjs/common";
17
26
  import { BehaviorSubject, combineLatest, filter, lastValueFrom, switchMap, take } from "rxjs";
18
27
  import * as PIXI from "pixi.js";
19
28
  //#region src/RpgClientEngine.ts
20
29
  var RpgClientEngine = class {
21
30
  constructor(context) {
22
31
  this.context = context;
32
+ this.sceneMapComponent = __ce_component$1;
23
33
  this.stopProcessingInput = false;
24
34
  this.width = signal("100%");
25
35
  this.height = signal("100%");
26
36
  this.spritesheets = /* @__PURE__ */ new Map();
27
37
  this.sounds = /* @__PURE__ */ new Map();
28
38
  this.componentAnimations = [];
39
+ this.pointer = createClientPointerContext();
29
40
  this.particleSettings = { emitters: [] };
30
41
  this.playerIdSignal = signal(null);
31
42
  this.spriteComponentsBehind = signal([]);
32
43
  this.spriteComponentsInFront = signal([]);
44
+ this.spriteComponents = /* @__PURE__ */ new Map();
33
45
  this.cameraFollowTargetId = signal(null);
34
46
  this.mapShakeTrigger = trigger();
35
47
  this.controlsReady = signal(void 0);
@@ -40,6 +52,7 @@ var RpgClientEngine = class {
40
52
  this.pendingPredictionFrames = [];
41
53
  this.lastClientPhysicsStepAt = 0;
42
54
  this.frameOffset = 0;
55
+ this.latestServerTickAt = 0;
43
56
  this.rtt = 0;
44
57
  this.pingInterval = null;
45
58
  this.PING_INTERVAL_MS = 5e3;
@@ -54,11 +67,13 @@ var RpgClientEngine = class {
54
67
  this.eventsReceived$ = new BehaviorSubject(false);
55
68
  this.sceneResetQueued = false;
56
69
  this.tickSubscriptions = [];
70
+ this.pendingSyncPackets = [];
57
71
  this.notificationManager = new NotificationManager();
58
72
  this.webSocket = inject(WebSocketToken);
59
73
  this.guiService = inject(RpgGui);
60
74
  this.loadMapService = inject(LoadMapToken);
61
75
  this.hooks = inject(ModulesToken);
76
+ this.projectiles = new ProjectileManager(this.hooks, (projectile) => this.predictProjectileImpact(projectile));
62
77
  this.globalConfig = inject(GlobalConfigToken);
63
78
  if (!this.globalConfig) this.globalConfig = {};
64
79
  if (!this.globalConfig.box) this.globalConfig.box = {
@@ -72,6 +87,12 @@ var RpgClientEngine = class {
72
87
  id: "animation",
73
88
  component: PrebuiltComponentAnimations.Animation
74
89
  });
90
+ this.registerSpriteComponent("rpg:text", __ce_component$2);
91
+ this.registerSpriteComponent("rpg:hpBar", __ce_component$3);
92
+ this.registerSpriteComponent("rpg:spBar", __ce_component$3);
93
+ this.registerSpriteComponent("rpg:bar", __ce_component$3);
94
+ this.registerSpriteComponent("rpg:shape", __ce_component$4);
95
+ this.registerSpriteComponent("rpg:image", __ce_component$5);
75
96
  this.predictionEnabled = this.globalConfig?.prediction?.enabled !== false;
76
97
  this.initializePredictionController();
77
98
  }
@@ -120,13 +141,26 @@ var RpgClientEngine = class {
120
141
  this.sceneMap = new RpgClientMap();
121
142
  this.sceneMap.configureClientPrediction(this.predictionEnabled);
122
143
  this.sceneMap.loadPhysic();
144
+ this.resolveSceneMapComponent();
145
+ inject(SaveClientService).initialize();
146
+ this.initListeners();
147
+ this.guiService._initialize();
148
+ try {
149
+ await this.webSocket.connection();
150
+ } catch (error) {
151
+ this.stopPingPong();
152
+ await this.callConnectError(error);
153
+ throw error;
154
+ }
123
155
  this.selector = document.body.querySelector("#rpg");
124
156
  const bootstrapOptions = this.globalConfig?.bootstrapCanvasOptions;
125
- const { app, canvasElement } = await bootstrapCanvas(this.selector, component, bootstrapOptions);
157
+ const { app, canvasElement } = await bootstrapCanvas(this.selector, __ce_component, bootstrapOptions);
126
158
  this.canvasApp = app;
127
159
  this.canvasElement = canvasElement;
128
160
  this.renderer = app.renderer;
161
+ this.setupPointerTracking();
129
162
  this.tick = canvasElement?.propObservables?.context["tick"].observable;
163
+ this.flushPendingSyncPackets();
130
164
  const inputCheckSubscription = this.tick.subscribe(() => {
131
165
  if (Date.now() - this.lastInputTime > 100) {
132
166
  const player = this.getCurrentPlayer();
@@ -144,6 +178,7 @@ var RpgClientEngine = class {
144
178
  this.hooks.callHooks("client-gui-load", this).subscribe();
145
179
  this.hooks.callHooks("client-particles-load", this).subscribe();
146
180
  this.hooks.callHooks("client-componentAnimations-load", this).subscribe();
181
+ this.hooks.callHooks("client-projectiles-load", this).subscribe();
147
182
  this.hooks.callHooks("client-sprite-load", this).subscribe();
148
183
  await lastValueFrom(this.hooks.callHooks("client-engine-onStart", this));
149
184
  this.resizeHandler = () => {
@@ -152,6 +187,7 @@ var RpgClientEngine = class {
152
187
  window.addEventListener("resize", this.resizeHandler);
153
188
  const tickSubscription = this.tick.subscribe((tick) => {
154
189
  this.stepClientPhysicsTick();
190
+ this.projectiles.step();
155
191
  this.flushPendingPredictedStates();
156
192
  this.flushPendingMovePath();
157
193
  this.hooks.callHooks("client-engine-onStep", this, tick).subscribe();
@@ -162,12 +198,46 @@ var RpgClientEngine = class {
162
198
  }
163
199
  });
164
200
  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
- });
201
+ this.startPingPong();
202
+ }
203
+ resolveSceneMapComponent() {
204
+ const components = this.hooks.getHookFunctions("client-sceneMap-component");
205
+ const component = components[components.length - 1];
206
+ if (component) this.sceneMapComponent = component;
207
+ }
208
+ setupPointerTracking() {
209
+ const renderer = this.renderer;
210
+ const canvas = renderer?.canvas ?? renderer?.view ?? this.canvasApp?.canvas;
211
+ if (!canvas || typeof canvas.addEventListener !== "function") return;
212
+ this.pointerCanvas = canvas;
213
+ this.pointerMoveHandler = (event) => {
214
+ const rect = canvas.getBoundingClientRect();
215
+ const screen = {
216
+ x: event.clientX - rect.left,
217
+ y: event.clientY - rect.top
218
+ };
219
+ const viewport = this.findViewportInstance();
220
+ let world = screen;
221
+ if (viewport && typeof viewport.toWorld === "function") {
222
+ const point = viewport.toWorld(screen.x, screen.y);
223
+ world = {
224
+ x: Number(point.x),
225
+ y: Number(point.y)
226
+ };
227
+ } else if (viewport && typeof viewport.toLocal === "function") {
228
+ const point = viewport.toLocal(screen);
229
+ world = {
230
+ x: Number(point.x),
231
+ y: Number(point.y)
232
+ };
233
+ }
234
+ this.pointer.update(screen, world);
235
+ };
236
+ canvas.addEventListener("pointermove", this.pointerMoveHandler);
237
+ canvas.addEventListener("pointerdown", this.pointerMoveHandler);
238
+ }
239
+ findViewportInstance() {
240
+ return (this.canvasApp?.stage?.children ?? []).find((child) => typeof child?.toWorld === "function" || child?.constructor?.name === "Viewport");
171
241
  }
172
242
  prepareSyncPayload(data) {
173
243
  const payload = { ...data ?? {} };
@@ -202,39 +272,27 @@ var RpgClientEngine = class {
202
272
  }
203
273
  initListeners() {
204
274
  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];
275
+ if (!this.tick) {
276
+ this.pendingSyncPackets.push(data);
277
+ return;
224
278
  }
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);
279
+ this.applySyncPacket(data);
228
280
  });
229
281
  this.webSocket.on("pong", (data) => {
230
- this.rtt = Date.now() - data.clientTime;
282
+ const now = Date.now();
283
+ this.rtt = now - data.clientTime;
231
284
  const estimatedTicksInFlight = Math.floor(this.rtt / 2 / (1e3 / 60));
232
285
  const estimatedServerTickNow = data.serverTick + estimatedTicksInFlight;
286
+ this.updateServerTickEstimate(estimatedServerTickNow, now);
233
287
  if (this.inputFrameCounter > 0) this.frameOffset = estimatedServerTickNow - data.clientFrame;
234
288
  console.debug(`[Ping/Pong] RTT: ${this.rtt}ms, ServerTick: ${data.serverTick}, FrameOffset: ${this.frameOffset}`);
235
289
  });
236
290
  this.webSocket.on("changeMap", (data) => {
237
291
  this.sceneResetQueued = true;
292
+ this.sceneMap.weatherState.set(null);
293
+ this.sceneMap.lightingState.set(null);
294
+ this.sceneMap.clearLightSpots();
295
+ this.projectiles.clear();
238
296
  this.cameraFollowTargetId.set(null);
239
297
  const transferToken = typeof data?.transferToken === "string" ? data.transferToken : void 0;
240
298
  this.loadScene(data.mapId, transferToken);
@@ -245,14 +303,34 @@ var RpgClientEngine = class {
245
303
  const player = object ? this.sceneMap.getObjectById(object) : void 0;
246
304
  this.getComponentAnimation(id).displayEffect(params, player || position);
247
305
  });
306
+ this.webSocket.on("projectile:spawnBatch", (data) => {
307
+ this.projectiles.spawnBatch(data?.projectiles ?? [], {
308
+ currentServerTick: this.estimateServerTick(),
309
+ tickDurationMs: this.getPhysicsTickDurationMs()
310
+ });
311
+ });
312
+ this.webSocket.on("projectile:impactBatch", (data) => {
313
+ this.projectiles.impactBatch(data?.impacts ?? []);
314
+ });
315
+ this.webSocket.on("projectile:destroyBatch", (data) => {
316
+ this.projectiles.destroyBatch(data?.projectiles ?? []);
317
+ });
318
+ this.webSocket.on("projectile:clear", () => {
319
+ this.projectiles.clear();
320
+ });
248
321
  this.webSocket.on("notification", (data) => {
249
322
  this.notificationManager.add(data);
250
323
  });
251
324
  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);
325
+ const { animationName, nbTimes, object, graphic, restoreAnimationName, restoreGraphics } = data;
326
+ const player = object ? this.sceneMap.getObjectById(object) : void 0;
327
+ if (!player) return;
328
+ const restoreOptions = {
329
+ restoreAnimationName,
330
+ restoreGraphics
331
+ };
332
+ if (graphic !== void 0) player.setAnimation(animationName, graphic, nbTimes, restoreOptions);
333
+ else player.setAnimation(animationName, nbTimes, restoreOptions);
256
334
  });
257
335
  this.webSocket.on("playSound", (data) => {
258
336
  const { soundId, volume, loop } = data;
@@ -314,6 +392,10 @@ var RpgClientEngine = class {
314
392
  seed: raw.seed
315
393
  });
316
394
  });
395
+ this.webSocket.on("lightingState", (data) => {
396
+ const raw = data && typeof data === "object" && "value" in data ? data.value : data;
397
+ this.sceneMap.lightingState.set(normalizeLightingState(raw));
398
+ });
317
399
  this.webSocket.on("open", () => {
318
400
  this.hooks.callHooks("client-engine-onConnected", this, this.socket).subscribe();
319
401
  this.startPingPong();
@@ -323,9 +405,46 @@ var RpgClientEngine = class {
323
405
  this.stopPingPong();
324
406
  });
325
407
  this.webSocket.on("error", (error) => {
326
- this.hooks.callHooks("client-engine-onConnectError", this, error, this.socket).subscribe();
408
+ this.callConnectError(error);
327
409
  });
328
410
  }
411
+ async callConnectError(error) {
412
+ await lastValueFrom(this.hooks.callHooks("client-engine-onConnectError", this, error, this.socket));
413
+ }
414
+ flushPendingSyncPackets() {
415
+ const packets = this.pendingSyncPackets;
416
+ this.pendingSyncPackets = [];
417
+ packets.forEach((packet) => this.applySyncPacket(packet));
418
+ }
419
+ applySyncPacket(data) {
420
+ if (data.pId) {
421
+ this.playerIdSignal.set(data.pId);
422
+ this.playerIdReceived$.next(true);
423
+ }
424
+ if (this.sceneResetQueued) {
425
+ const weatherState = this.sceneMap.weatherState();
426
+ const lightingState = this.sceneMap.lightingState();
427
+ this.sceneMap.reset();
428
+ this.sceneMap.weatherState.set(weatherState);
429
+ this.sceneMap.lightingState.set(lightingState);
430
+ this.sceneMap.loadPhysic();
431
+ this.sceneResetQueued = false;
432
+ }
433
+ this.hooks.callHooks("client-sceneMap-onChanges", this.sceneMap, { partial: data }).subscribe();
434
+ const ack = data?.ack;
435
+ const normalizedAck = ack && typeof ack.frame === "number" ? this.normalizeAckWithSyncState(ack, data) : void 0;
436
+ const payload = this.prepareSyncPayload(data);
437
+ load(this.sceneMap, payload, true);
438
+ if (normalizedAck) this.applyServerAck(normalizedAck);
439
+ for (const playerId in payload.players ?? {}) {
440
+ const player = payload.players[playerId];
441
+ if (!player._param) continue;
442
+ for (const param in player._param) this.sceneMap.players()[playerId]._param()[param] = player._param[param];
443
+ }
444
+ const players = payload.players || this.sceneMap.players();
445
+ if (players && Object.keys(players).length > 0) this.playersReceived$.next(true);
446
+ if ((payload.events || this.sceneMap.events()) !== void 0) this.eventsReceived$.next(true);
447
+ }
329
448
  /**
330
449
  * Start periodic ping/pong for client-server synchronization
331
450
  *
@@ -402,11 +521,17 @@ var RpgClientEngine = class {
402
521
  room: mapId,
403
522
  query: transferToken ? { transferToken } : void 0
404
523
  });
405
- await this.webSocket.reconnect(() => {
406
- inject(SaveClientService).initialize(this.webSocket);
407
- this.initListeners();
408
- this.guiService._initialize();
409
- });
524
+ try {
525
+ await this.webSocket.reconnect(() => {
526
+ inject(SaveClientService).initialize();
527
+ this.initListeners();
528
+ this.guiService._initialize();
529
+ });
530
+ } catch (error) {
531
+ this.stopPingPong();
532
+ await this.callConnectError(error);
533
+ throw error;
534
+ }
410
535
  const res = await this.loadMapService.load(mapId);
411
536
  this.sceneMap.data.set(res);
412
537
  if (this.playerIdSignal()) this.playerIdReceived$.next(true);
@@ -458,7 +583,7 @@ var RpgClientEngine = class {
458
583
  * If not found and a resolver is set, it calls the resolver to create the spritesheet.
459
584
  * The resolved spritesheet is automatically cached for future use.
460
585
  *
461
- * @param id - The spritesheet ID to retrieve
586
+ * @param id - The spritesheet ID or legacy tile ID to retrieve
462
587
  * @returns The spritesheet if found or created, or undefined if not found and no resolver
463
588
  * @returns Promise<any> if the resolver is asynchronous
464
589
  *
@@ -807,6 +932,41 @@ var RpgClientEngine = class {
807
932
  return component;
808
933
  }
809
934
  /**
935
+ * Register a reusable sprite component that can be addressed by the server.
936
+ *
937
+ * Server-side component definitions only carry the component id and
938
+ * serializable props. The client registry maps that id to the CanvasEngine
939
+ * component that performs the actual rendering.
940
+ *
941
+ * @param id - Stable component id used by server component definitions
942
+ * @param component - CanvasEngine component to render for this id
943
+ * @returns The registered component
944
+ *
945
+ * @example
946
+ * ```ts
947
+ * engine.registerSpriteComponent('guildBadge', GuildBadgeComponent);
948
+ * ```
949
+ */
950
+ registerSpriteComponent(id, component) {
951
+ this.spriteComponents.set(id, component);
952
+ return component;
953
+ }
954
+ /**
955
+ * Get a reusable sprite component by id.
956
+ *
957
+ * @param id - Component id registered on the client
958
+ * @returns The CanvasEngine component, or undefined when missing
959
+ */
960
+ getSpriteComponent(id) {
961
+ return this.spriteComponents.get(id);
962
+ }
963
+ registerProjectileComponent(type, component) {
964
+ return this.projectiles.register(type, component);
965
+ }
966
+ getProjectileComponent(type) {
967
+ return this.projectiles.get(type);
968
+ }
969
+ /**
810
970
  * Add a component animation to the engine
811
971
  *
812
972
  * Component animations are temporary visual effects that can be displayed
@@ -882,13 +1042,34 @@ var RpgClientEngine = class {
882
1042
  * duration: 1000,
883
1043
  * onFinish: () => console.log('Fade complete')
884
1044
  * });
1045
+ *
1046
+ * // Wait until the transition component calls onFinish
1047
+ * await engine.startTransition('fade', { duration: 1000 });
885
1048
  * ```
886
1049
  */
887
1050
  startTransition(id, props = {}) {
888
1051
  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);
1052
+ return new Promise((resolve) => {
1053
+ let finished = false;
1054
+ const finish = (data) => {
1055
+ if (finished) return;
1056
+ finished = true;
1057
+ props?.onFinish?.(data);
1058
+ resolve();
1059
+ };
1060
+ this.guiService.display(id, {
1061
+ ...props,
1062
+ onFinish: finish
1063
+ });
1064
+ });
890
1065
  }
891
1066
  async processInput({ input }) {
1067
+ if (this.stopProcessingInput) return;
1068
+ const currentPlayer = this.sceneMap.getCurrentPlayer();
1069
+ if (!(!currentPlayer || getCanMoveValue(currentPlayer))) {
1070
+ this.interruptCurrentPlayerMovement(currentPlayer);
1071
+ return;
1072
+ }
892
1073
  const timestamp = Date.now();
893
1074
  let frame;
894
1075
  let tick;
@@ -905,7 +1086,6 @@ var RpgClientEngine = class {
905
1086
  input,
906
1087
  playerId: this.playerId
907
1088
  }).subscribe();
908
- const currentPlayer = this.sceneMap.getCurrentPlayer();
909
1089
  const bodyReady = this.ensureCurrentPlayerBody();
910
1090
  if (currentPlayer && bodyReady) {
911
1091
  currentPlayer.changeDirection(input);
@@ -918,13 +1098,18 @@ var RpgClientEngine = class {
918
1098
  this.emitMovePacket(input, frame, tick, timestamp, true);
919
1099
  this.lastInputTime = Date.now();
920
1100
  }
921
- processAction({ action }) {
1101
+ processAction(action, data) {
922
1102
  if (this.stopProcessingInput) return;
1103
+ const currentPlayer = this.sceneMap.getCurrentPlayer();
1104
+ if (!(!currentPlayer || getCanMoveValue(currentPlayer))) return;
1105
+ const payload = normalizeActionInput(action, data);
923
1106
  this.hooks.callHooks("client-engine-onInput", this, {
924
- input: "action",
1107
+ input: payload.action,
1108
+ action: payload.action,
1109
+ data: payload.data,
925
1110
  playerId: this.playerId
926
1111
  }).subscribe();
927
- this.webSocket.emit("action", { action });
1112
+ this.webSocket.emit("action", payload);
928
1113
  }
929
1114
  get PIXI() {
930
1115
  return PIXI;
@@ -941,6 +1126,37 @@ var RpgClientEngine = class {
941
1126
  getPhysicsTick() {
942
1127
  return this.sceneMap?.getTick?.() ?? 0;
943
1128
  }
1129
+ getPhysicsTickDurationMs() {
1130
+ const timeStep = this.sceneMap?.physic?.getWorld?.()?.getTimeStep?.();
1131
+ return typeof timeStep === "number" && Number.isFinite(timeStep) && timeStep > 0 ? timeStep * 1e3 : 1e3 / 60;
1132
+ }
1133
+ updateServerTickEstimate(serverTick, now = Date.now()) {
1134
+ if (typeof serverTick !== "number" || !Number.isFinite(serverTick)) return;
1135
+ this.latestServerTick = serverTick;
1136
+ this.latestServerTickAt = now;
1137
+ }
1138
+ estimateServerTick(now = Date.now()) {
1139
+ if (typeof this.latestServerTick !== "number" || this.latestServerTickAt <= 0) return;
1140
+ const elapsedTicks = Math.max(0, (now - this.latestServerTickAt) / this.getPhysicsTickDurationMs());
1141
+ return this.latestServerTick + elapsedTicks;
1142
+ }
1143
+ predictProjectileImpact(projectile) {
1144
+ if (projectile.predictImpact === false) return null;
1145
+ const sceneMap = this.sceneMap;
1146
+ if (!sceneMap?.physic || !Number.isFinite(projectile.range) || projectile.range <= 0) return null;
1147
+ const origin = projectile.origin;
1148
+ const direction = projectile.direction;
1149
+ 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;
1150
+ 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);
1151
+ if (!hit) return null;
1152
+ return {
1153
+ id: projectile.id,
1154
+ targetId: hit.entity.uuid,
1155
+ x: hit.point.x,
1156
+ y: hit.point.y,
1157
+ distance: hit.distance
1158
+ };
1159
+ }
944
1160
  ensureCurrentPlayerBody() {
945
1161
  const player = this.sceneMap?.getCurrentPlayer();
946
1162
  const myId = this.playerIdSignal();
@@ -1008,6 +1224,11 @@ var RpgClientEngine = class {
1008
1224
  }
1009
1225
  flushPendingMovePath() {
1010
1226
  if (!this.predictionEnabled || !this.prediction) return;
1227
+ const player = this.sceneMap?.getCurrentPlayer?.();
1228
+ if (player && !getCanMoveValue(player)) {
1229
+ this.interruptCurrentPlayerMovement(player);
1230
+ return;
1231
+ }
1011
1232
  const pendingInputs = this.prediction.getPendingInputs();
1012
1233
  if (pendingInputs.length === 0) return;
1013
1234
  const latest = pendingInputs[pendingInputs.length - 1];
@@ -1124,6 +1345,31 @@ var RpgClientEngine = class {
1124
1345
  this.lastMovePathSentFrame = 0;
1125
1346
  }
1126
1347
  /**
1348
+ * Stop local movement immediately and discard pending predicted movement.
1349
+ *
1350
+ * Use this before a blocking action such as an A-RPG attack, dialog, dash
1351
+ * startup, or any client-side state where already buffered movement inputs
1352
+ * must not be replayed after server reconciliation.
1353
+ *
1354
+ * @param player - Player object to stop. Defaults to the current player.
1355
+ * @returns `true` when a player was found and interrupted.
1356
+ *
1357
+ * @example
1358
+ * ```ts
1359
+ * engine.interruptCurrentPlayerMovement();
1360
+ * ```
1361
+ */
1362
+ interruptCurrentPlayerMovement(player = this.sceneMap?.getCurrentPlayer?.()) {
1363
+ if (!player) return false;
1364
+ this.sceneMap?.stopMovement?.(player);
1365
+ this.prediction?.clearPendingInputs();
1366
+ this.pendingPredictionFrames = [];
1367
+ this.lastInputTime = 0;
1368
+ this.lastMovePathSentAt = Date.now();
1369
+ this.lastMovePathSentFrame = this.inputFrameCounter;
1370
+ return true;
1371
+ }
1372
+ /**
1127
1373
  * Trigger a flash animation on a sprite
1128
1374
  *
1129
1375
  * This method allows you to trigger a flash effect on any sprite from client-side code.
@@ -1177,6 +1423,7 @@ var RpgClientEngine = class {
1177
1423
  if (sprite && typeof sprite.flash === "function") sprite.flash(options);
1178
1424
  }
1179
1425
  applyServerAck(ack) {
1426
+ this.updateServerTickEstimate(ack.serverTick);
1180
1427
  if (this.predictionEnabled && this.prediction) {
1181
1428
  const result = this.prediction.applyServerAck({
1182
1429
  frame: ack.frame,
@@ -1205,6 +1452,10 @@ var RpgClientEngine = class {
1205
1452
  reconcilePrediction(authoritativeState, pendingInputs) {
1206
1453
  const player = this.getCurrentPlayer();
1207
1454
  if (!player) return;
1455
+ if (!getCanMoveValue(player)) {
1456
+ this.interruptCurrentPlayerMovement(player);
1457
+ return;
1458
+ }
1208
1459
  this.sceneMap.stopMovement(player);
1209
1460
  this.applyAuthoritativeState(authoritativeState);
1210
1461
  if (!pendingInputs.length) return;
@@ -1277,6 +1528,12 @@ var RpgClientEngine = class {
1277
1528
  window.removeEventListener("resize", this.resizeHandler);
1278
1529
  this.resizeHandler = void 0;
1279
1530
  }
1531
+ if (this.pointerMoveHandler && this.pointerCanvas) {
1532
+ this.pointerCanvas.removeEventListener("pointermove", this.pointerMoveHandler);
1533
+ this.pointerCanvas.removeEventListener("pointerdown", this.pointerMoveHandler);
1534
+ this.pointerMoveHandler = void 0;
1535
+ this.pointerCanvas = void 0;
1536
+ }
1280
1537
  const rendererStillExists = this.renderer && typeof this.renderer.destroy === "function";
1281
1538
  if (this.canvasApp && typeof this.canvasApp.destroy === "function") {
1282
1539
  try {