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

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 (66) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/Game/AnimationManager.d.ts +1 -0
  3. package/dist/Game/AnimationManager.js +3 -0
  4. package/dist/Game/AnimationManager.js.map +1 -1
  5. package/dist/Game/ClientVisuals.d.ts +61 -0
  6. package/dist/Game/ClientVisuals.js +96 -0
  7. package/dist/Game/ClientVisuals.js.map +1 -0
  8. package/dist/Game/ClientVisuals.spec.d.ts +1 -0
  9. package/dist/Game/EventComponentResolver.d.ts +16 -0
  10. package/dist/Game/EventComponentResolver.js +52 -0
  11. package/dist/Game/EventComponentResolver.js.map +1 -0
  12. package/dist/Game/EventComponentResolver.spec.d.ts +1 -0
  13. package/dist/Game/Map.js +9 -0
  14. package/dist/Game/Map.js.map +1 -1
  15. package/dist/Game/Object.js +2 -2
  16. package/dist/Game/Object.js.map +1 -1
  17. package/dist/Game/Object.spec.d.ts +1 -0
  18. package/dist/Game/ProjectileManager.d.ts +11 -2
  19. package/dist/Game/ProjectileManager.js +19 -2
  20. package/dist/Game/ProjectileManager.js.map +1 -1
  21. package/dist/RpgClient.d.ts +64 -0
  22. package/dist/RpgClientEngine.d.ts +57 -0
  23. package/dist/RpgClientEngine.js +110 -14
  24. package/dist/RpgClientEngine.js.map +1 -1
  25. package/dist/components/animations/fx.ce.js +58 -0
  26. package/dist/components/animations/fx.ce.js.map +1 -0
  27. package/dist/components/animations/index.d.ts +1 -0
  28. package/dist/components/animations/index.js +3 -1
  29. package/dist/components/animations/index.js.map +1 -1
  30. package/dist/components/character.ce.js +111 -13
  31. package/dist/components/character.ce.js.map +1 -1
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.js +3 -2
  34. package/dist/module.js +7 -0
  35. package/dist/module.js.map +1 -1
  36. package/dist/services/actionInput.d.ts +3 -1
  37. package/dist/services/actionInput.js +33 -1
  38. package/dist/services/actionInput.js.map +1 -1
  39. package/dist/services/standalone.d.ts +3 -1
  40. package/dist/services/standalone.js +31 -13
  41. package/dist/services/standalone.js.map +1 -1
  42. package/dist/utils/mapId.d.ts +1 -0
  43. package/dist/utils/mapId.js +6 -0
  44. package/dist/utils/mapId.js.map +1 -0
  45. package/package.json +3 -3
  46. package/src/Game/AnimationManager.ts +4 -0
  47. package/src/Game/ClientVisuals.spec.ts +56 -0
  48. package/src/Game/ClientVisuals.ts +184 -0
  49. package/src/Game/EventComponentResolver.spec.ts +84 -0
  50. package/src/Game/EventComponentResolver.ts +74 -0
  51. package/src/Game/Map.ts +10 -0
  52. package/src/Game/Object.spec.ts +46 -0
  53. package/src/Game/Object.ts +2 -2
  54. package/src/Game/ProjectileManager.spec.ts +111 -0
  55. package/src/Game/ProjectileManager.ts +24 -2
  56. package/src/RpgClient.ts +68 -0
  57. package/src/RpgClientEngine.ts +130 -16
  58. package/src/components/animations/fx.ce +101 -0
  59. package/src/components/animations/index.ts +4 -2
  60. package/src/components/character.ce +154 -11
  61. package/src/index.ts +1 -0
  62. package/src/module.ts +11 -0
  63. package/src/services/actionInput.spec.ts +54 -0
  64. package/src/services/actionInput.ts +68 -1
  65. package/src/services/standalone.ts +39 -10
  66. package/src/utils/mapId.ts +2 -0
@@ -2,8 +2,10 @@ import { ComponentFunction, Signal } from 'canvasengine';
2
2
  import { RpgClientEngine } from './RpgClientEngine';
3
3
  import { Loader, Container } from 'pixi.js';
4
4
  import { RpgClientObject } from './Game/Object';
5
+ import { RpgClientEvent } from './Game/Event';
5
6
  import { MapPhysicsEntityContext, MapPhysicsInitContext, RpgActionName } from '@rpgjs/common';
6
7
  import { ClientProjectileSpawn, RenderedProjectileProps } from './Game/ProjectileManager';
8
+ import { ClientVisualMap } from './Game/ClientVisuals';
7
9
  type RpgComponent = RpgClientObject;
8
10
  type SceneMap = Container;
9
11
  export type SpriteComponentConfig = ComponentFunction | {
@@ -12,6 +14,14 @@ export type SpriteComponentConfig = ComponentFunction | {
12
14
  data?: Record<string, any> | ((object: RpgClientObject) => Record<string, any>);
13
15
  dependencies?: (object: RpgClientObject) => any[];
14
16
  };
17
+ export type EventComponentSprite = RpgClientEvent & Record<string, any>;
18
+ export type EventComponentConfig = ComponentFunction | {
19
+ component: ComponentFunction;
20
+ props?: Record<string, any> | ((event: EventComponentSprite) => Record<string, any>);
21
+ data?: Record<string, any> | ((event: EventComponentSprite) => Record<string, any>);
22
+ dependencies?: (event: EventComponentSprite) => any[];
23
+ renderGraphic?: boolean;
24
+ };
15
25
  export interface RpgSpriteBeforeRemoveContext {
16
26
  reason?: string;
17
27
  data?: any;
@@ -131,6 +141,30 @@ export interface RpgSpriteHooks {
131
141
  * ```
132
142
  */
133
143
  components?: Record<string, ComponentFunction>;
144
+ /**
145
+ * Resolve a custom CanvasEngine component for a specific event.
146
+ *
147
+ * The component always receives the synced event object as the `sprite` prop.
148
+ * Custom props are merged in addition to `sprite`, but cannot replace it.
149
+ * Return `null` or `undefined` to keep the default graphic renderer.
150
+ *
151
+ * @prop { (event: EventComponentSprite) => EventComponentConfig | null | undefined } [eventComponent]
152
+ * @memberof RpgSpriteHooks
153
+ * @example
154
+ * ```ts
155
+ * import ChestEvent from './components/chest-event.ce'
156
+ *
157
+ * const sprite: RpgSpriteHooks = {
158
+ * eventComponent(sprite) {
159
+ * if (sprite.name === 'CHEST') {
160
+ * return ChestEvent
161
+ * }
162
+ * return null
163
+ * }
164
+ * }
165
+ * ```
166
+ */
167
+ eventComponent?: (event: EventComponentSprite) => EventComponentConfig | null | undefined;
134
168
  /**
135
169
  * As soon as the sprite is initialized
136
170
  *
@@ -675,6 +709,36 @@ export interface RpgClient {
675
709
  id: string;
676
710
  component: ComponentFunction;
677
711
  }[];
712
+ /**
713
+ * Named client-side visual macros.
714
+ *
715
+ * Use client visuals when the server needs to trigger a group of existing
716
+ * client visual primitives at once, such as a flash, damage text, sound,
717
+ * component animation, and camera shake. The server sends only the visual
718
+ * name and a serializable payload; the rendering details live on the client.
719
+ *
720
+ * For a single sound, flash, or component animation, prefer the direct
721
+ * server APIs (`playSound`, `flash`, `showComponentAnimation`). Client
722
+ * visuals are meant to group several visual operations and reduce bandwidth.
723
+ *
724
+ * ```ts
725
+ * import { defineModule, RpgClient } from '@rpgjs/client'
726
+ *
727
+ * export default defineModule<RpgClient>({
728
+ * clientVisuals: {
729
+ * hit({ target, data }, helpers) {
730
+ * helpers.flash(target, { type: 'tint', tint: 'red' })
731
+ * helpers.showHit(target, `-${data.damage}`)
732
+ * helpers.sound('hit')
733
+ * }
734
+ * }
735
+ * })
736
+ * ```
737
+ *
738
+ * @prop {Record<string, ClientVisualHandler>} [clientVisuals]
739
+ * @memberof RpgClient
740
+ */
741
+ clientVisuals?: ClientVisualMap;
678
742
  /**
679
743
  * Client-side projectile rendering configuration.
680
744
  *
@@ -1,11 +1,15 @@
1
1
  import { Trigger } from 'canvasengine';
2
2
  import { AbstractWebsocket } from './services/AbstractSocket';
3
3
  import { Direction, RpgActionInput, RpgActionName } from '@rpgjs/common';
4
+ import { EventComponentConfig } from './RpgClient';
5
+ import { RpgClientEvent } from './Game/Event';
4
6
  import { RpgClientMap } from './Game/Map';
5
7
  import { AnimationManager } from './Game/AnimationManager';
6
8
  import { Observable } from 'rxjs';
7
9
  import { ProjectileManager } from './Game/ProjectileManager';
10
+ import { ClientVisualRegistry, ClientVisualHandler, ClientVisualMap, ClientVisualPacket } from './Game/ClientVisuals';
8
11
  import { ClientPointerContext } from './services/pointerContext';
12
+ import { EventComponentResolver } from './Game/EventComponentResolver';
9
13
  import * as PIXI from "pixi.js";
10
14
  type ConfigurableTrigger<T> = Omit<Trigger<T>, "start"> & {
11
15
  start(config?: T): Promise<void>;
@@ -33,6 +37,7 @@ export declare class RpgClientEngine<T = any> {
33
37
  spritesheets: Map<string | number, any>;
34
38
  sounds: Map<string, any>;
35
39
  componentAnimations: any[];
40
+ clientVisuals: ClientVisualRegistry;
36
41
  projectiles: ProjectileManager;
37
42
  pointer: ClientPointerContext;
38
43
  private spritesheetResolver?;
@@ -48,6 +53,7 @@ export declare class RpgClientEngine<T = any> {
48
53
  spriteComponentsBehind: import('canvasengine').WritableArraySignal<any[]>;
49
54
  spriteComponentsInFront: import('canvasengine').WritableArraySignal<any[]>;
50
55
  spriteComponents: Map<string, any>;
56
+ private eventComponentResolvers;
51
57
  /** ID of the sprite that the camera should follow. null means follow the current player */
52
58
  cameraFollowTargetId: import('canvasengine').WritableSignal<string | null>;
53
59
  /** Trigger for map shake animation */
@@ -77,6 +83,9 @@ export declare class RpgClientEngine<T = any> {
77
83
  private eventsReceived$;
78
84
  private onAfterLoadingSubscription?;
79
85
  private sceneResetQueued;
86
+ private mapTransitionInProgress;
87
+ private currentMapRoomId?;
88
+ private socketListenersInitialized;
80
89
  private tickSubscriptions;
81
90
  private resizeHandler?;
82
91
  private pointerMoveHandler?;
@@ -125,6 +134,9 @@ export declare class RpgClientEngine<T = any> {
125
134
  private prepareSyncPayload;
126
135
  private normalizeAckWithSyncState;
127
136
  private initListeners;
137
+ private beginMapTransfer;
138
+ private clearComponentAnimations;
139
+ private shouldProcessProjectilePacket;
128
140
  private callConnectError;
129
141
  private flushPendingSyncPackets;
130
142
  private applySyncPacket;
@@ -502,8 +514,52 @@ export declare class RpgClientEngine<T = any> {
502
514
  * @returns The CanvasEngine component, or undefined when missing
503
515
  */
504
516
  getSpriteComponent(id: string): any;
517
+ /**
518
+ * Register a custom event component resolver.
519
+ *
520
+ * The last resolver returning a component wins. This lets later modules
521
+ * override earlier defaults without replacing the whole map scene.
522
+ *
523
+ * @param resolver - Function receiving the synced event object
524
+ * @returns The registered resolver
525
+ */
526
+ addEventComponentResolver(resolver: EventComponentResolver): EventComponentResolver;
527
+ /**
528
+ * Resolve the custom CanvasEngine component for an event, if any.
529
+ *
530
+ * @param event - Synced client event object
531
+ * @returns The component/config returned by the last matching resolver
532
+ */
533
+ resolveEventComponent(event: RpgClientEvent): EventComponentConfig | null;
505
534
  registerProjectileComponent(type: string, component: any): any;
506
535
  getProjectileComponent(type: string): any;
536
+ /**
537
+ * Register a named client visual macro.
538
+ *
539
+ * Client visuals are small client-side functions that group existing visual
540
+ * primitives such as flash, sound, component animations, sprite animation, or
541
+ * map shake. The server sends only the visual name and a serializable payload.
542
+ *
543
+ * @param name - Stable visual name sent by the server
544
+ * @param handler - Client-side visual handler
545
+ * @returns The registered handler
546
+ */
547
+ registerClientVisual(name: string, handler: ClientVisualHandler): ClientVisualHandler;
548
+ /**
549
+ * Register several named client visual macros.
550
+ *
551
+ * @param visuals - Map of visual names to client-side handlers
552
+ */
553
+ registerClientVisuals(visuals: ClientVisualMap): void;
554
+ /**
555
+ * Play a registered client visual locally.
556
+ *
557
+ * This is also used by the websocket listener when the server calls
558
+ * `player.clientVisual()` or `map.clientVisual()`.
559
+ *
560
+ * @param packet - Visual name and serializable payload
561
+ */
562
+ playClientVisual(packet: ClientVisualPacket): Promise<void>;
507
563
  /**
508
564
  * Add a component animation to the engine
509
565
  *
@@ -588,6 +644,7 @@ export declare class RpgClientEngine<T = any> {
588
644
  get socket(): AbstractWebsocket;
589
645
  get playerId(): string | null;
590
646
  get scene(): RpgClientMap;
647
+ getObjectById(id: string): any;
591
648
  private getPhysicsTick;
592
649
  private getPhysicsTickDurationMs;
593
650
  private updateServerTickEstimate;
@@ -19,8 +19,11 @@ import __ce_component$3 from "./components/dynamics/bar.ce.js";
19
19
  import __ce_component$4 from "./components/dynamics/shape.ce.js";
20
20
  import __ce_component$5 from "./components/dynamics/image.ce.js";
21
21
  import { NotificationManager } from "./Gui/NotificationManager.js";
22
+ import { normalizeRoomMapId } from "./utils/mapId.js";
22
23
  import { ProjectileManager } from "./Game/ProjectileManager.js";
24
+ import { ClientVisualRegistry } from "./Game/ClientVisuals.js";
23
25
  import { createClientPointerContext } from "./services/pointerContext.js";
26
+ import { EventComponentResolverRegistry } from "./Game/EventComponentResolver.js";
24
27
  import { Howl, bootstrapCanvas, signal, trigger } from "canvasengine";
25
28
  import { Direction, ModulesToken, PredictionController, Vector2, normalizeLightingState } from "@rpgjs/common";
26
29
  import { BehaviorSubject, combineLatest, filter, lastValueFrom, switchMap, take } from "rxjs";
@@ -36,12 +39,14 @@ var RpgClientEngine = class {
36
39
  this.spritesheets = /* @__PURE__ */ new Map();
37
40
  this.sounds = /* @__PURE__ */ new Map();
38
41
  this.componentAnimations = [];
42
+ this.clientVisuals = new ClientVisualRegistry();
39
43
  this.pointer = createClientPointerContext();
40
44
  this.particleSettings = { emitters: [] };
41
45
  this.playerIdSignal = signal(null);
42
46
  this.spriteComponentsBehind = signal([]);
43
47
  this.spriteComponentsInFront = signal([]);
44
48
  this.spriteComponents = /* @__PURE__ */ new Map();
49
+ this.eventComponentResolvers = new EventComponentResolverRegistry();
45
50
  this.cameraFollowTargetId = signal(null);
46
51
  this.mapShakeTrigger = trigger();
47
52
  this.controlsReady = signal(void 0);
@@ -66,6 +71,8 @@ var RpgClientEngine = class {
66
71
  this.playersReceived$ = new BehaviorSubject(false);
67
72
  this.eventsReceived$ = new BehaviorSubject(false);
68
73
  this.sceneResetQueued = false;
74
+ this.mapTransitionInProgress = false;
75
+ this.socketListenersInitialized = false;
69
76
  this.tickSubscriptions = [];
70
77
  this.pendingSyncPackets = [];
71
78
  this.notificationManager = new NotificationManager();
@@ -178,6 +185,7 @@ var RpgClientEngine = class {
178
185
  this.hooks.callHooks("client-gui-load", this).subscribe();
179
186
  this.hooks.callHooks("client-particles-load", this).subscribe();
180
187
  this.hooks.callHooks("client-componentAnimations-load", this).subscribe();
188
+ this.hooks.callHooks("client-clientVisuals-load", this).subscribe();
181
189
  this.hooks.callHooks("client-projectiles-load", this).subscribe();
182
190
  this.hooks.callHooks("client-sprite-load", this).subscribe();
183
191
  await lastValueFrom(this.hooks.callHooks("client-engine-onStart", this));
@@ -271,6 +279,8 @@ var RpgClientEngine = class {
271
279
  };
272
280
  }
273
281
  initListeners() {
282
+ if (this.socketListenersInitialized) return;
283
+ this.socketListenersInitialized = true;
274
284
  this.webSocket.on("sync", (data) => {
275
285
  if (!this.tick) {
276
286
  this.pendingSyncPackets.push(data);
@@ -288,12 +298,8 @@ var RpgClientEngine = class {
288
298
  console.debug(`[Ping/Pong] RTT: ${this.rtt}ms, ServerTick: ${data.serverTick}, FrameOffset: ${this.frameOffset}`);
289
299
  });
290
300
  this.webSocket.on("changeMap", (data) => {
291
- this.sceneResetQueued = true;
292
- this.sceneMap.weatherState.set(null);
293
- this.sceneMap.lightingState.set(null);
294
- this.sceneMap.clearLightSpots();
295
- this.projectiles.clear();
296
- this.cameraFollowTargetId.set(null);
301
+ const nextMapId = typeof data?.mapId === "string" ? data.mapId : void 0;
302
+ this.beginMapTransfer(nextMapId);
297
303
  const transferToken = typeof data?.transferToken === "string" ? data.transferToken : void 0;
298
304
  this.loadScene(data.mapId, transferToken);
299
305
  });
@@ -303,19 +309,27 @@ var RpgClientEngine = class {
303
309
  const player = object ? this.sceneMap.getObjectById(object) : void 0;
304
310
  this.getComponentAnimation(id).displayEffect(params, player || position);
305
311
  });
312
+ this.webSocket.on("clientVisual", (data) => {
313
+ this.playClientVisual(data);
314
+ });
306
315
  this.webSocket.on("projectile:spawnBatch", (data) => {
316
+ if (!this.shouldProcessProjectilePacket(data)) return;
307
317
  this.projectiles.spawnBatch(data?.projectiles ?? [], {
318
+ mapId: data?.mapId,
308
319
  currentServerTick: this.estimateServerTick(),
309
320
  tickDurationMs: this.getPhysicsTickDurationMs()
310
321
  });
311
322
  });
312
323
  this.webSocket.on("projectile:impactBatch", (data) => {
313
- this.projectiles.impactBatch(data?.impacts ?? []);
324
+ if (!this.shouldProcessProjectilePacket(data)) return;
325
+ this.projectiles.impactBatch(data?.impacts ?? [], { mapId: data?.mapId });
314
326
  });
315
327
  this.webSocket.on("projectile:destroyBatch", (data) => {
316
- this.projectiles.destroyBatch(data?.projectiles ?? []);
328
+ if (!this.shouldProcessProjectilePacket(data)) return;
329
+ this.projectiles.destroyBatch(data?.projectiles ?? [], { mapId: data?.mapId });
317
330
  });
318
- this.webSocket.on("projectile:clear", () => {
331
+ this.webSocket.on("projectile:clear", (data) => {
332
+ if (!this.shouldProcessProjectilePacket(data)) return;
319
333
  this.projectiles.clear();
320
334
  });
321
335
  this.webSocket.on("notification", (data) => {
@@ -408,6 +422,31 @@ var RpgClientEngine = class {
408
422
  this.callConnectError(error);
409
423
  });
410
424
  }
425
+ beginMapTransfer(nextMapId) {
426
+ this.mapTransitionInProgress = true;
427
+ this.currentMapRoomId = nextMapId;
428
+ this.sceneResetQueued = false;
429
+ this.clearClientPredictionStates();
430
+ this.sceneMap.weatherState.set(null);
431
+ this.sceneMap.lightingState.set(null);
432
+ this.sceneMap.clearLightSpots();
433
+ this.clearComponentAnimations();
434
+ this.projectiles.setMapId(nextMapId);
435
+ this.cameraFollowTargetId.set(null);
436
+ this.sceneMap.reset();
437
+ this.sceneMap.loadPhysic();
438
+ }
439
+ clearComponentAnimations() {
440
+ this.componentAnimations.forEach((componentAnimation) => {
441
+ componentAnimation.instance?.clear?.();
442
+ });
443
+ }
444
+ shouldProcessProjectilePacket(data) {
445
+ if (this.mapTransitionInProgress) return false;
446
+ const packetMapId = normalizeRoomMapId(typeof data?.mapId === "string" ? data.mapId : void 0);
447
+ const currentMapId = normalizeRoomMapId(this.currentMapRoomId);
448
+ return !packetMapId || !currentMapId || packetMapId === currentMapId;
449
+ }
411
450
  async callConnectError(error) {
412
451
  await lastValueFrom(this.hooks.callHooks("client-engine-onConnectError", this, error, this.socket));
413
452
  }
@@ -522,12 +561,9 @@ var RpgClientEngine = class {
522
561
  query: transferToken ? { transferToken } : void 0
523
562
  });
524
563
  try {
525
- await this.webSocket.reconnect(() => {
526
- inject(SaveClientService).initialize();
527
- this.initListeners();
528
- this.guiService._initialize();
529
- });
564
+ await this.webSocket.reconnect();
530
565
  } catch (error) {
566
+ this.mapTransitionInProgress = false;
531
567
  this.stopPingPong();
532
568
  await this.callConnectError(error);
533
569
  throw error;
@@ -539,6 +575,8 @@ var RpgClientEngine = class {
539
575
  if (players && Object.keys(players).length > 0) this.playersReceived$.next(true);
540
576
  if (this.sceneMap.events() !== void 0) this.eventsReceived$.next(true);
541
577
  this.mapLoadCompleted$.next(true);
578
+ this.currentMapRoomId = mapId;
579
+ this.mapTransitionInProgress = false;
542
580
  this.sceneMap.configureClientPrediction(this.predictionEnabled);
543
581
  this.sceneMap.loadPhysic();
544
582
  }
@@ -960,6 +998,27 @@ var RpgClientEngine = class {
960
998
  getSpriteComponent(id) {
961
999
  return this.spriteComponents.get(id);
962
1000
  }
1001
+ /**
1002
+ * Register a custom event component resolver.
1003
+ *
1004
+ * The last resolver returning a component wins. This lets later modules
1005
+ * override earlier defaults without replacing the whole map scene.
1006
+ *
1007
+ * @param resolver - Function receiving the synced event object
1008
+ * @returns The registered resolver
1009
+ */
1010
+ addEventComponentResolver(resolver) {
1011
+ return this.eventComponentResolvers.add(resolver);
1012
+ }
1013
+ /**
1014
+ * Resolve the custom CanvasEngine component for an event, if any.
1015
+ *
1016
+ * @param event - Synced client event object
1017
+ * @returns The component/config returned by the last matching resolver
1018
+ */
1019
+ resolveEventComponent(event) {
1020
+ return this.eventComponentResolvers.resolve(event);
1021
+ }
963
1022
  registerProjectileComponent(type, component) {
964
1023
  return this.projectiles.register(type, component);
965
1024
  }
@@ -967,6 +1026,39 @@ var RpgClientEngine = class {
967
1026
  return this.projectiles.get(type);
968
1027
  }
969
1028
  /**
1029
+ * Register a named client visual macro.
1030
+ *
1031
+ * Client visuals are small client-side functions that group existing visual
1032
+ * primitives such as flash, sound, component animations, sprite animation, or
1033
+ * map shake. The server sends only the visual name and a serializable payload.
1034
+ *
1035
+ * @param name - Stable visual name sent by the server
1036
+ * @param handler - Client-side visual handler
1037
+ * @returns The registered handler
1038
+ */
1039
+ registerClientVisual(name, handler) {
1040
+ return this.clientVisuals.register(name, handler);
1041
+ }
1042
+ /**
1043
+ * Register several named client visual macros.
1044
+ *
1045
+ * @param visuals - Map of visual names to client-side handlers
1046
+ */
1047
+ registerClientVisuals(visuals) {
1048
+ this.clientVisuals.registerMany(visuals);
1049
+ }
1050
+ /**
1051
+ * Play a registered client visual locally.
1052
+ *
1053
+ * This is also used by the websocket listener when the server calls
1054
+ * `player.clientVisual()` or `map.clientVisual()`.
1055
+ *
1056
+ * @param packet - Visual name and serializable payload
1057
+ */
1058
+ playClientVisual(packet) {
1059
+ return this.clientVisuals.play(packet, this);
1060
+ }
1061
+ /**
970
1062
  * Add a component animation to the engine
971
1063
  *
972
1064
  * Component animations are temporary visual effects that can be displayed
@@ -1123,6 +1215,9 @@ var RpgClientEngine = class {
1123
1215
  get scene() {
1124
1216
  return this.sceneMap;
1125
1217
  }
1218
+ getObjectById(id) {
1219
+ return this.sceneMap?.getObjectById(id);
1220
+ }
1126
1221
  getPhysicsTick() {
1127
1222
  return this.sceneMap?.getTick?.() ?? 0;
1128
1223
  }
@@ -1565,6 +1660,7 @@ var RpgClientEngine = class {
1565
1660
  this.cameraFollowTargetId.set(null);
1566
1661
  this.spriteComponentsBehind.set([]);
1567
1662
  this.spriteComponentsInFront.set([]);
1663
+ this.eventComponentResolvers.clear();
1568
1664
  this.spritesheets.clear();
1569
1665
  this.sounds.clear();
1570
1666
  this.componentAnimations = [];