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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/CHANGELOG.md +37 -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/Gui/Gui.d.ts +17 -4
  16. package/dist/Gui/Gui.js +78 -48
  17. package/dist/Gui/Gui.js.map +1 -1
  18. package/dist/Gui/Gui.spec.d.ts +1 -0
  19. package/dist/Gui/NotificationManager.js.map +1 -1
  20. package/dist/Resource.js +1 -1
  21. package/dist/Resource.js.map +1 -1
  22. package/dist/RpgClient.d.ts +57 -2
  23. package/dist/RpgClientEngine.d.ts +61 -6
  24. package/dist/RpgClientEngine.js +122 -14
  25. package/dist/RpgClientEngine.js.map +1 -1
  26. package/dist/Sound.js.map +1 -1
  27. package/dist/_virtual/{_@oxc-project_runtime@0.122.0 → _@oxc-project_runtime@0.130.0}/helpers/decorate.js +1 -1
  28. package/dist/_virtual/{_@oxc-project_runtime@0.122.0 → _@oxc-project_runtime@0.130.0}/helpers/decorateMetadata.js +1 -1
  29. package/dist/components/animations/animation.ce.js +4 -5
  30. package/dist/components/animations/animation.ce.js.map +1 -1
  31. package/dist/components/animations/hit.ce.js +19 -25
  32. package/dist/components/animations/hit.ce.js.map +1 -1
  33. package/dist/components/animations/index.js +4 -4
  34. package/dist/components/animations/index.js.map +1 -1
  35. package/dist/components/character.ce.js +406 -226
  36. package/dist/components/character.ce.js.map +1 -1
  37. package/dist/components/dynamics/bar.ce.js +96 -0
  38. package/dist/components/dynamics/bar.ce.js.map +1 -0
  39. package/dist/components/dynamics/image.ce.js +23 -0
  40. package/dist/components/dynamics/image.ce.js.map +1 -0
  41. package/dist/components/dynamics/parse-value.d.ts +3 -0
  42. package/dist/components/dynamics/parse-value.js +54 -35
  43. package/dist/components/dynamics/parse-value.js.map +1 -1
  44. package/dist/components/dynamics/parse-value.spec.d.ts +1 -0
  45. package/dist/components/dynamics/shape-utils.d.ts +16 -0
  46. package/dist/components/dynamics/shape-utils.js +73 -0
  47. package/dist/components/dynamics/shape-utils.js.map +1 -0
  48. package/dist/components/dynamics/shape-utils.spec.d.ts +1 -0
  49. package/dist/components/dynamics/shape.ce.js +83 -0
  50. package/dist/components/dynamics/shape.ce.js.map +1 -0
  51. package/dist/components/dynamics/text.ce.js +33 -56
  52. package/dist/components/dynamics/text.ce.js.map +1 -1
  53. package/dist/components/gui/box.ce.js +6 -8
  54. package/dist/components/gui/box.ce.js.map +1 -1
  55. package/dist/components/gui/dialogbox/index.ce.js +53 -60
  56. package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
  57. package/dist/components/gui/gameover.ce.js +39 -63
  58. package/dist/components/gui/gameover.ce.js.map +1 -1
  59. package/dist/components/gui/hud/hud.ce.js +21 -30
  60. package/dist/components/gui/hud/hud.ce.js.map +1 -1
  61. package/dist/components/gui/menu/equip-menu.ce.js +110 -164
  62. package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
  63. package/dist/components/gui/menu/exit-menu.ce.js +6 -5
  64. package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
  65. package/dist/components/gui/menu/items-menu.ce.js +49 -67
  66. package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
  67. package/dist/components/gui/menu/main-menu.ce.js +72 -90
  68. package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
  69. package/dist/components/gui/menu/options-menu.ce.js +5 -4
  70. package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
  71. package/dist/components/gui/menu/skills-menu.ce.js +12 -17
  72. package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
  73. package/dist/components/gui/mobile/index.js +2 -2
  74. package/dist/components/gui/mobile/index.js.map +1 -1
  75. package/dist/components/gui/mobile/mobile.ce.js +5 -4
  76. package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
  77. package/dist/components/gui/notification/notification.ce.js +22 -24
  78. package/dist/components/gui/notification/notification.ce.js.map +1 -1
  79. package/dist/components/gui/save-load.ce.js +70 -248
  80. package/dist/components/gui/save-load.ce.js.map +1 -1
  81. package/dist/components/gui/shop/shop.ce.js +87 -125
  82. package/dist/components/gui/shop/shop.ce.js.map +1 -1
  83. package/dist/components/gui/title-screen.ce.js +42 -68
  84. package/dist/components/gui/title-screen.ce.js.map +1 -1
  85. package/dist/components/player-components-utils.d.ts +67 -0
  86. package/dist/components/player-components-utils.js +162 -0
  87. package/dist/components/player-components-utils.js.map +1 -0
  88. package/dist/components/player-components-utils.spec.d.ts +1 -0
  89. package/dist/components/player-components.ce.js +188 -0
  90. package/dist/components/player-components.ce.js.map +1 -0
  91. package/dist/components/prebuilt/hp-bar.ce.js +41 -44
  92. package/dist/components/prebuilt/hp-bar.ce.js.map +1 -1
  93. package/dist/components/prebuilt/light-halo.ce.js +35 -59
  94. package/dist/components/prebuilt/light-halo.ce.js.map +1 -1
  95. package/dist/components/scenes/canvas.ce.js +157 -21
  96. package/dist/components/scenes/canvas.ce.js.map +1 -1
  97. package/dist/components/scenes/draw-map.ce.js +19 -29
  98. package/dist/components/scenes/draw-map.ce.js.map +1 -1
  99. package/dist/components/scenes/event-layer.ce.js +9 -8
  100. package/dist/components/scenes/event-layer.ce.js.map +1 -1
  101. package/dist/core/inject.js +1 -1
  102. package/dist/core/inject.js.map +1 -1
  103. package/dist/core/setup.js +1 -1
  104. package/dist/core/setup.js.map +1 -1
  105. package/dist/decorators/spritesheet.d.ts +1 -0
  106. package/dist/decorators/spritesheet.js +11 -0
  107. package/dist/decorators/spritesheet.js.map +1 -0
  108. package/dist/index.d.ts +1 -0
  109. package/dist/index.js +21 -20
  110. package/dist/module.js +4 -1
  111. package/dist/module.js.map +1 -1
  112. package/dist/node_modules/.pnpm/{@signe_di@2.9.0 → @signe_di@3.0.1}/node_modules/@signe/di/dist/index.js +7 -117
  113. package/dist/node_modules/.pnpm/@signe_di@3.0.1/node_modules/@signe/di/dist/index.js.map +1 -0
  114. package/dist/node_modules/.pnpm/@signe_reactive@3.0.1/node_modules/@signe/reactive/dist/index.js +239 -0
  115. package/dist/node_modules/.pnpm/@signe_reactive@3.0.1/node_modules/@signe/reactive/dist/index.js.map +1 -0
  116. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/chunk-EUXUH3YW.js +13 -0
  117. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/chunk-EUXUH3YW.js.map +1 -0
  118. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/index.js +696 -0
  119. package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/index.js.map +1 -0
  120. package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/client/index.js +44 -0
  121. package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/client/index.js.map +1 -0
  122. package/dist/node_modules/.pnpm/{@signe_sync@2.9.0 → @signe_sync@3.0.1}/node_modules/@signe/sync/dist/index.js +57 -141
  123. package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/index.js.map +1 -0
  124. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-HAC622V3.js.map +1 -1
  125. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-S74YV6PU.js.map +1 -1
  126. package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js +27 -27
  127. package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js.map +1 -1
  128. package/dist/presets/animation.js.map +1 -1
  129. package/dist/presets/faceset.js.map +1 -1
  130. package/dist/presets/icon.js.map +1 -1
  131. package/dist/presets/index.js.map +1 -1
  132. package/dist/presets/lpc.js.map +1 -1
  133. package/dist/presets/rmspritesheet.js.map +1 -1
  134. package/dist/services/AbstractSocket.js.map +1 -1
  135. package/dist/services/keyboardControls.js.map +1 -1
  136. package/dist/services/loadMap.d.ts +6 -0
  137. package/dist/services/loadMap.js +1 -1
  138. package/dist/services/loadMap.js.map +1 -1
  139. package/dist/services/mmorpg.js +9 -4
  140. package/dist/services/mmorpg.js.map +1 -1
  141. package/dist/services/save.js.map +1 -1
  142. package/dist/services/save.spec.d.ts +1 -0
  143. package/dist/services/standalone.js +1 -1
  144. package/dist/services/standalone.js.map +1 -1
  145. package/dist/utils/getEntityProp.js +4 -3
  146. package/dist/utils/getEntityProp.js.map +1 -1
  147. package/dist/utils/getEntityProp.spec.d.ts +1 -0
  148. package/dist/utils/readPropValue.d.ts +2 -0
  149. package/dist/utils/readPropValue.js +13 -0
  150. package/dist/utils/readPropValue.js.map +1 -0
  151. package/package.json +13 -14
  152. package/src/Game/AnimationManager.spec.ts +30 -0
  153. package/src/Game/AnimationManager.ts +22 -10
  154. package/src/Game/Map.ts +91 -2
  155. package/src/Game/Object.ts +148 -69
  156. package/src/Gui/Gui.spec.ts +273 -0
  157. package/src/Gui/Gui.ts +105 -50
  158. package/src/Resource.ts +1 -2
  159. package/src/RpgClient.ts +63 -2
  160. package/src/RpgClientEngine.ts +173 -25
  161. package/src/components/character.ce +422 -15
  162. package/src/components/dynamics/bar.ce +87 -0
  163. package/src/components/dynamics/image.ce +20 -0
  164. package/src/components/dynamics/parse-value.spec.ts +83 -0
  165. package/src/components/dynamics/parse-value.ts +111 -37
  166. package/src/components/dynamics/shape-utils.spec.ts +46 -0
  167. package/src/components/dynamics/shape-utils.ts +61 -0
  168. package/src/components/dynamics/shape.ce +89 -0
  169. package/src/components/dynamics/text.ce +34 -149
  170. package/src/components/gui/dialogbox/index.ce +15 -6
  171. package/src/components/player-components-utils.spec.ts +109 -0
  172. package/src/components/player-components-utils.ts +205 -0
  173. package/src/components/player-components.ce +221 -0
  174. package/src/components/scenes/canvas.ce +165 -6
  175. package/src/components/scenes/draw-map.ce +2 -15
  176. package/src/components/scenes/event-layer.ce +1 -2
  177. package/src/core/setup.ts +2 -2
  178. package/src/decorators/spritesheet.ts +8 -0
  179. package/src/index.ts +1 -0
  180. package/src/module.ts +5 -1
  181. package/src/services/loadMap.ts +2 -0
  182. package/src/services/mmorpg.ts +8 -2
  183. package/src/services/save.spec.ts +127 -0
  184. package/src/utils/getEntityProp.spec.ts +96 -0
  185. package/src/utils/getEntityProp.ts +4 -3
  186. package/src/utils/readPropValue.ts +16 -0
  187. package/dist/node_modules/.pnpm/@signe_di@2.9.0/node_modules/@signe/di/dist/index.js.map +0 -1
  188. package/dist/node_modules/.pnpm/@signe_reactive@2.8.3/node_modules/@signe/reactive/dist/index.js +0 -457
  189. package/dist/node_modules/.pnpm/@signe_reactive@2.8.3/node_modules/@signe/reactive/dist/index.js.map +0 -1
  190. package/dist/node_modules/.pnpm/@signe_reactive@2.9.0/node_modules/@signe/reactive/dist/index.js +0 -463
  191. package/dist/node_modules/.pnpm/@signe_reactive@2.9.0/node_modules/@signe/reactive/dist/index.js.map +0 -1
  192. package/dist/node_modules/.pnpm/@signe_room@2.9.0/node_modules/@signe/room/dist/index.js +0 -2191
  193. package/dist/node_modules/.pnpm/@signe_room@2.9.0/node_modules/@signe/room/dist/index.js.map +0 -1
  194. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/chunk-7QVYU63E.js +0 -10
  195. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/chunk-7QVYU63E.js.map +0 -1
  196. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/client/index.js +0 -91
  197. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/client/index.js.map +0 -1
  198. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/index.js.map +0 -1
  199. package/dist/node_modules/.pnpm/dset@3.1.4/node_modules/dset/dist/index.js +0 -14
  200. package/dist/node_modules/.pnpm/dset@3.1.4/node_modules/dset/dist/index.js.map +0 -1
@@ -1,11 +1,11 @@
1
1
  import Canvas from "./components/scenes/canvas.ce";
2
2
  import { inject } from './core/inject'
3
- import { signal, bootstrapCanvas, Howl, trigger } from "canvasengine";
3
+ import { signal, bootstrapCanvas, Howl, trigger, type Trigger } from "canvasengine";
4
4
  import { AbstractWebsocket, WebSocketToken } from "./services/AbstractSocket";
5
5
  import { LoadMapService, LoadMapToken } from "./services/loadMap";
6
6
  import { RpgSound } from "./Sound";
7
7
  import { RpgResource } from "./Resource";
8
- import { Hooks, ModulesToken, Direction } from "@rpgjs/common";
8
+ import { Hooks, ModulesToken, Direction, normalizeLightingState } from "@rpgjs/common";
9
9
  import { load } from "@signe/sync";
10
10
  import { RpgClientMap } from "./Game/Map"
11
11
  import { RpgGui } from "./Gui/Gui";
@@ -14,6 +14,10 @@ import { lastValueFrom, Observable, combineLatest, BehaviorSubject, filter, swit
14
14
  import { GlobalConfigToken } from "./module";
15
15
  import * as PIXI from "pixi.js";
16
16
  import { PrebuiltComponentAnimations } from "./components/animations";
17
+ import TextComponent from "./components/dynamics/text.ce";
18
+ import BarComponent from "./components/dynamics/bar.ce";
19
+ import ShapeComponent from "./components/dynamics/shape.ce";
20
+ import ImageComponent from "./components/dynamics/image.ce";
17
21
  import {
18
22
  PredictionController,
19
23
  type PredictionHistoryEntry,
@@ -21,6 +25,7 @@ import {
21
25
  } from "@rpgjs/common";
22
26
  import { NotificationManager } from "./Gui/NotificationManager";
23
27
  import { SaveClientService } from "./services/save";
28
+ import { getCanMoveValue } from "./utils/readPropValue";
24
29
 
25
30
  interface MovementTrajectoryPoint {
26
31
  frame: number;
@@ -32,6 +37,17 @@ interface MovementTrajectoryPoint {
32
37
  direction?: Direction;
33
38
  }
34
39
 
40
+ type ConfigurableTrigger<T> = Omit<Trigger<T>, "start"> & {
41
+ start(config?: T): Promise<void>;
42
+ };
43
+
44
+ type MapShakeOptions = {
45
+ intensity?: number;
46
+ duration?: number;
47
+ frequency?: number;
48
+ direction?: string;
49
+ };
50
+
35
51
  export class RpgClientEngine<T = any> {
36
52
  private guiService: RpgGui;
37
53
  private webSocket: AbstractWebsocket;
@@ -44,10 +60,10 @@ export class RpgClientEngine<T = any> {
44
60
  stopProcessingInput = false;
45
61
  width = signal("100%");
46
62
  height = signal("100%");
47
- spritesheets: Map<string, any> = new Map();
63
+ spritesheets: Map<string | number, any> = new Map();
48
64
  sounds: Map<string, any> = new Map();
49
65
  componentAnimations: any[] = [];
50
- private spritesheetResolver?: (id: string) => any | Promise<any>;
66
+ private spritesheetResolver?: (id: string | number) => any | Promise<any>;
51
67
  private soundResolver?: (id: string) => any | Promise<any>;
52
68
  particleSettings: {
53
69
  emitters: any[]
@@ -61,10 +77,11 @@ export class RpgClientEngine<T = any> {
61
77
  playerIdSignal = signal<string | null>(null);
62
78
  spriteComponentsBehind = signal<any[]>([]);
63
79
  spriteComponentsInFront = signal<any[]>([]);
80
+ spriteComponents: Map<string, any> = new Map();
64
81
  /** ID of the sprite that the camera should follow. null means follow the current player */
65
82
  cameraFollowTargetId = signal<string | null>(null);
66
83
  /** Trigger for map shake animation */
67
- mapShakeTrigger = trigger();
84
+ mapShakeTrigger: ConfigurableTrigger<MapShakeOptions> = trigger<MapShakeOptions>();
68
85
 
69
86
  controlsReady = signal(undefined);
70
87
  gamePause = signal(false);
@@ -123,6 +140,13 @@ export class RpgClientEngine<T = any> {
123
140
  component: PrebuiltComponentAnimations.Animation
124
141
  })
125
142
 
143
+ this.registerSpriteComponent("rpg:text", TextComponent);
144
+ this.registerSpriteComponent("rpg:hpBar", BarComponent);
145
+ this.registerSpriteComponent("rpg:spBar", BarComponent);
146
+ this.registerSpriteComponent("rpg:bar", BarComponent);
147
+ this.registerSpriteComponent("rpg:shape", ShapeComponent);
148
+ this.registerSpriteComponent("rpg:image", ImageComponent);
149
+
126
150
  this.predictionEnabled = (this.globalConfig as any)?.prediction?.enabled !== false;
127
151
  this.initializePredictionController();
128
152
  }
@@ -233,7 +257,7 @@ export class RpgClientEngine<T = any> {
233
257
 
234
258
  await this.webSocket.connection(() => {
235
259
  const saveClient = inject(SaveClientService);
236
- saveClient.initialize(this.webSocket);
260
+ saveClient.initialize();
237
261
  this.initListeners()
238
262
  this.guiService._initialize()
239
263
  this.startPingPong();
@@ -355,6 +379,7 @@ export class RpgClientEngine<T = any> {
355
379
 
356
380
  this.webSocket.on("changeMap", (data) => {
357
381
  this.sceneResetQueued = true;
382
+ this.sceneMap.clearLightSpots();
358
383
  // Reset camera follow to default (follow current player) when changing maps
359
384
  this.cameraFollowTargetId.set(null);
360
385
  const transferToken = typeof data?.transferToken === "string" ? data.transferToken : undefined;
@@ -374,15 +399,27 @@ export class RpgClientEngine<T = any> {
374
399
  this.notificationManager.add(data);
375
400
  });
376
401
 
377
- this.webSocket.on("setAnimation", (data) => {
378
- const { animationName, nbTimes, object, graphic } = data;
379
- const player = this.sceneMap.getObjectById(object);
380
- if (graphic !== undefined) {
381
- player.setAnimation(animationName, graphic, nbTimes);
382
- } else {
383
- player.setAnimation(animationName, nbTimes);
384
- }
385
- })
402
+ this.webSocket.on("setAnimation", (data) => {
403
+ const {
404
+ animationName,
405
+ nbTimes,
406
+ object,
407
+ graphic,
408
+ restoreAnimationName,
409
+ restoreGraphics,
410
+ } = data;
411
+ const player = object ? this.sceneMap.getObjectById(object) : undefined;
412
+ if (!player) return;
413
+ const restoreOptions = {
414
+ restoreAnimationName,
415
+ restoreGraphics,
416
+ };
417
+ if (graphic !== undefined) {
418
+ player.setAnimation(animationName, graphic, nbTimes, restoreOptions);
419
+ } else {
420
+ player.setAnimation(animationName, nbTimes, restoreOptions);
421
+ }
422
+ })
386
423
 
387
424
  this.webSocket.on("playSound", (data) => {
388
425
  const { soundId, volume, loop } = data;
@@ -413,7 +450,7 @@ export class RpgClientEngine<T = any> {
413
450
 
414
451
  this.webSocket.on("shakeMap", (data) => {
415
452
  const { intensity, duration, frequency, direction } = data || {};
416
- (this.mapShakeTrigger as any).start({
453
+ this.mapShakeTrigger.start({
417
454
  intensity,
418
455
  duration,
419
456
  frequency,
@@ -447,6 +484,14 @@ export class RpgClientEngine<T = any> {
447
484
  });
448
485
  });
449
486
 
487
+ this.webSocket.on("lightingState", (data) => {
488
+ const raw = (data && typeof data === "object" && "value" in data)
489
+ ? (data as any).value
490
+ : data;
491
+
492
+ this.sceneMap.lightingState.set(normalizeLightingState(raw));
493
+ });
494
+
450
495
  this.webSocket.on('open', () => {
451
496
  this.hooks.callHooks("client-engine-onConnected", this, this.socket).subscribe();
452
497
  // Start ping/pong for synchronization
@@ -562,7 +607,7 @@ export class RpgClientEngine<T = any> {
562
607
  })
563
608
  await this.webSocket.reconnect(() => {
564
609
  const saveClient = inject(SaveClientService);
565
- saveClient.initialize(this.webSocket);
610
+ saveClient.initialize();
566
611
  this.initListeners()
567
612
  this.guiService._initialize()
568
613
  })
@@ -623,7 +668,7 @@ export class RpgClientEngine<T = any> {
623
668
  * });
624
669
  * ```
625
670
  */
626
- setSpritesheetResolver(resolver: (id: string) => any | Promise<any>): void {
671
+ setSpritesheetResolver(resolver: (id: string | number) => any | Promise<any>): void {
627
672
  this.spritesheetResolver = resolver;
628
673
  }
629
674
 
@@ -634,7 +679,7 @@ export class RpgClientEngine<T = any> {
634
679
  * If not found and a resolver is set, it calls the resolver to create the spritesheet.
635
680
  * The resolved spritesheet is automatically cached for future use.
636
681
  *
637
- * @param id - The spritesheet ID to retrieve
682
+ * @param id - The spritesheet ID or legacy tile ID to retrieve
638
683
  * @returns The spritesheet if found or created, or undefined if not found and no resolver
639
684
  * @returns Promise<any> if the resolver is asynchronous
640
685
  *
@@ -647,7 +692,7 @@ export class RpgClientEngine<T = any> {
647
692
  * const spritesheet = await engine.getSpriteSheet('dynamic-sprite');
648
693
  * ```
649
694
  */
650
- getSpriteSheet(id: string): any | Promise<any> {
695
+ getSpriteSheet(id: string | number): any | Promise<any> {
651
696
  // Check cache first
652
697
  if (this.spritesheets.has(id)) {
653
698
  return this.spritesheets.get(id);
@@ -1081,6 +1126,37 @@ export class RpgClientEngine<T = any> {
1081
1126
  return component
1082
1127
  }
1083
1128
 
1129
+ /**
1130
+ * Register a reusable sprite component that can be addressed by the server.
1131
+ *
1132
+ * Server-side component definitions only carry the component id and
1133
+ * serializable props. The client registry maps that id to the CanvasEngine
1134
+ * component that performs the actual rendering.
1135
+ *
1136
+ * @param id - Stable component id used by server component definitions
1137
+ * @param component - CanvasEngine component to render for this id
1138
+ * @returns The registered component
1139
+ *
1140
+ * @example
1141
+ * ```ts
1142
+ * engine.registerSpriteComponent('guildBadge', GuildBadgeComponent);
1143
+ * ```
1144
+ */
1145
+ registerSpriteComponent(id: string, component: any) {
1146
+ this.spriteComponents.set(id, component);
1147
+ return component;
1148
+ }
1149
+
1150
+ /**
1151
+ * Get a reusable sprite component by id.
1152
+ *
1153
+ * @param id - Component id registered on the client
1154
+ * @returns The CanvasEngine component, or undefined when missing
1155
+ */
1156
+ getSpriteComponent(id: string) {
1157
+ return this.spriteComponents.get(id);
1158
+ }
1159
+
1084
1160
  /**
1085
1161
  * Add a component animation to the engine
1086
1162
  *
@@ -1164,16 +1240,43 @@ export class RpgClientEngine<T = any> {
1164
1240
  * duration: 1000,
1165
1241
  * onFinish: () => console.log('Fade complete')
1166
1242
  * });
1243
+ *
1244
+ * // Wait until the transition component calls onFinish
1245
+ * await engine.startTransition('fade', { duration: 1000 });
1167
1246
  * ```
1168
1247
  */
1169
- startTransition(id: string, props: any = {}) {
1248
+ startTransition(id: string, props: any = {}): Promise<void> {
1170
1249
  if (!this.guiService.exists(id)) {
1171
1250
  throw new Error(`Transition with id ${id} not found. Make sure to add it using engine.addTransition() or in your module's transitions property.`);
1172
1251
  }
1173
- this.guiService.display(id, props);
1252
+ return new Promise<void>((resolve) => {
1253
+ let finished = false;
1254
+ const finish = (data?: any) => {
1255
+ if (finished) return;
1256
+ finished = true;
1257
+ props?.onFinish?.(data);
1258
+ resolve();
1259
+ };
1260
+
1261
+ this.guiService.display(id, {
1262
+ ...props,
1263
+ onFinish: finish,
1264
+ });
1265
+ });
1174
1266
  }
1175
1267
 
1176
1268
  async processInput({ input }: { input: Direction }) {
1269
+ if (this.stopProcessingInput) return;
1270
+
1271
+ const currentPlayer = this.sceneMap.getCurrentPlayer() as any;
1272
+ const canMove =
1273
+ !currentPlayer ||
1274
+ getCanMoveValue(currentPlayer);
1275
+ if (!canMove) {
1276
+ this.interruptCurrentPlayerMovement(currentPlayer);
1277
+ return;
1278
+ }
1279
+
1177
1280
  const timestamp = Date.now();
1178
1281
  let frame: number;
1179
1282
  let tick: number;
@@ -1188,7 +1291,6 @@ export class RpgClientEngine<T = any> {
1188
1291
  this.inputFrameCounter = frame;
1189
1292
  this.hooks.callHooks("client-engine-onInput", this, { input, playerId: this.playerId }).subscribe();
1190
1293
 
1191
- const currentPlayer = this.sceneMap.getCurrentPlayer();
1192
1294
  const bodyReady = this.ensureCurrentPlayerBody();
1193
1295
  if (currentPlayer && bodyReady) {
1194
1296
  currentPlayer.changeDirection(input);
@@ -1207,6 +1309,12 @@ export class RpgClientEngine<T = any> {
1207
1309
 
1208
1310
  processAction({ action }: { action: number }) {
1209
1311
  if (this.stopProcessingInput) return;
1312
+ const currentPlayer = this.sceneMap.getCurrentPlayer() as any;
1313
+ const canMove =
1314
+ !currentPlayer ||
1315
+ getCanMoveValue(currentPlayer);
1316
+ if (!canMove) return;
1317
+
1210
1318
  this.hooks.callHooks("client-engine-onInput", this, { input: 'action', playerId: this.playerId }).subscribe();
1211
1319
  this.webSocket.emit('action', { action })
1212
1320
  }
@@ -1337,6 +1445,14 @@ export class RpgClientEngine<T = any> {
1337
1445
  if (!this.predictionEnabled || !this.prediction) {
1338
1446
  return;
1339
1447
  }
1448
+ const player = this.sceneMap?.getCurrentPlayer?.() as any;
1449
+ if (
1450
+ player &&
1451
+ !getCanMoveValue(player)
1452
+ ) {
1453
+ this.interruptCurrentPlayerMovement(player);
1454
+ return;
1455
+ }
1340
1456
  const pendingInputs = this.prediction.getPendingInputs();
1341
1457
  if (pendingInputs.length === 0) {
1342
1458
  return;
@@ -1475,6 +1591,34 @@ export class RpgClientEngine<T = any> {
1475
1591
  this.lastMovePathSentFrame = 0;
1476
1592
  }
1477
1593
 
1594
+ /**
1595
+ * Stop local movement immediately and discard pending predicted movement.
1596
+ *
1597
+ * Use this before a blocking action such as an A-RPG attack, dialog, dash
1598
+ * startup, or any client-side state where already buffered movement inputs
1599
+ * must not be replayed after server reconciliation.
1600
+ *
1601
+ * @param player - Player object to stop. Defaults to the current player.
1602
+ * @returns `true` when a player was found and interrupted.
1603
+ *
1604
+ * @example
1605
+ * ```ts
1606
+ * engine.interruptCurrentPlayerMovement();
1607
+ * ```
1608
+ */
1609
+ interruptCurrentPlayerMovement(player: any = this.sceneMap?.getCurrentPlayer?.()): boolean {
1610
+ if (!player) {
1611
+ return false;
1612
+ }
1613
+ (this.sceneMap as any)?.stopMovement?.(player);
1614
+ this.prediction?.clearPendingInputs();
1615
+ this.pendingPredictionFrames = [];
1616
+ this.lastInputTime = 0;
1617
+ this.lastMovePathSentAt = Date.now();
1618
+ this.lastMovePathSentFrame = this.inputFrameCounter;
1619
+ return true;
1620
+ }
1621
+
1478
1622
  /**
1479
1623
  * Trigger a flash animation on a sprite
1480
1624
  *
@@ -1560,7 +1704,7 @@ export class RpgClientEngine<T = any> {
1560
1704
  if (typeof ack.x !== "number" || typeof ack.y !== "number") {
1561
1705
  return;
1562
1706
  }
1563
- const player = this.getCurrentPlayer();
1707
+ const player = this.getCurrentPlayer() as any;
1564
1708
  const myId = this.playerIdSignal();
1565
1709
  if (!player || !myId) {
1566
1710
  return;
@@ -1583,10 +1727,14 @@ export class RpgClientEngine<T = any> {
1583
1727
  authoritativeState: PredictionState<Direction>,
1584
1728
  pendingInputs: PredictionHistoryEntry<Direction>[],
1585
1729
  ): void {
1586
- const player = this.getCurrentPlayer();
1730
+ const player = this.getCurrentPlayer() as any;
1587
1731
  if (!player) {
1588
1732
  return;
1589
1733
  }
1734
+ if (!getCanMoveValue(player)) {
1735
+ this.interruptCurrentPlayerMovement(player);
1736
+ return;
1737
+ }
1590
1738
 
1591
1739
  (this.sceneMap as any).stopMovement(player);
1592
1740
  this.applyAuthoritativeState(authoritativeState);