@rpgjs/client 5.0.0-alpha.21 → 5.0.0-alpha.23

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 (135) hide show
  1. package/dist/Game/Object.d.ts +111 -0
  2. package/dist/Game/TransitionManager.d.ts +56 -0
  3. package/dist/RpgClientEngine.d.ts +306 -9
  4. package/dist/components/gui/mobile/index.d.ts +8 -0
  5. package/dist/components/prebuilt/index.d.ts +1 -0
  6. package/dist/index.d.ts +7 -1
  7. package/dist/index.js +14 -8
  8. package/dist/index.js.map +1 -1
  9. package/dist/index10.js +1 -1
  10. package/dist/index11.js +6 -5
  11. package/dist/index11.js.map +1 -1
  12. package/dist/index12.js +2 -2
  13. package/dist/index13.js +102 -10
  14. package/dist/index13.js.map +1 -1
  15. package/dist/index14.js +68 -9
  16. package/dist/index14.js.map +1 -1
  17. package/dist/index15.js +10 -224
  18. package/dist/index15.js.map +1 -1
  19. package/dist/index16.js +9 -97
  20. package/dist/index16.js.map +1 -1
  21. package/dist/index17.js +300 -89
  22. package/dist/index17.js.map +1 -1
  23. package/dist/index18.js +63 -80
  24. package/dist/index18.js.map +1 -1
  25. package/dist/index19.js +96 -348
  26. package/dist/index19.js.map +1 -1
  27. package/dist/index2.js +387 -21
  28. package/dist/index2.js.map +1 -1
  29. package/dist/index20.js +361 -5
  30. package/dist/index20.js.map +1 -1
  31. package/dist/index21.js +19 -50
  32. package/dist/index21.js.map +1 -1
  33. package/dist/index22.js +212 -5
  34. package/dist/index22.js.map +1 -1
  35. package/dist/index23.js +6 -395
  36. package/dist/index23.js.map +1 -1
  37. package/dist/index24.js +4 -39
  38. package/dist/index24.js.map +1 -1
  39. package/dist/index25.js +19 -20
  40. package/dist/index25.js.map +1 -1
  41. package/dist/index26.js +44 -2624
  42. package/dist/index26.js.map +1 -1
  43. package/dist/index27.js +5 -110
  44. package/dist/index27.js.map +1 -1
  45. package/dist/index28.js +394 -65
  46. package/dist/index28.js.map +1 -1
  47. package/dist/index29.js +40 -15
  48. package/dist/index29.js.map +1 -1
  49. package/dist/index3.js +3 -3
  50. package/dist/index30.js +21 -23
  51. package/dist/index30.js.map +1 -1
  52. package/dist/index31.js +49 -91
  53. package/dist/index31.js.map +1 -1
  54. package/dist/index32.js +2624 -32
  55. package/dist/index32.js.map +1 -1
  56. package/dist/index33.js +108 -18
  57. package/dist/index33.js.map +1 -1
  58. package/dist/index34.js +69 -3
  59. package/dist/index34.js.map +1 -1
  60. package/dist/index35.js +17 -331
  61. package/dist/index35.js.map +1 -1
  62. package/dist/index36.js +24 -24
  63. package/dist/index36.js.map +1 -1
  64. package/dist/index37.js +92 -8
  65. package/dist/index37.js.map +1 -1
  66. package/dist/index38.js +37 -7
  67. package/dist/index38.js.map +1 -1
  68. package/dist/index39.js +22 -10
  69. package/dist/index39.js.map +1 -1
  70. package/dist/index4.js +3 -3
  71. package/dist/index40.js +140 -6
  72. package/dist/index40.js.map +1 -1
  73. package/dist/index41.js +31 -3678
  74. package/dist/index41.js.map +1 -1
  75. package/dist/index42.js +3 -185
  76. package/dist/index42.js.map +1 -1
  77. package/dist/index43.js +172 -489
  78. package/dist/index43.js.map +1 -1
  79. package/dist/index44.js +498 -71
  80. package/dist/index44.js.map +1 -1
  81. package/dist/index45.js +331 -2
  82. package/dist/index45.js.map +1 -1
  83. package/dist/index46.js +25 -11
  84. package/dist/index46.js.map +1 -1
  85. package/dist/index47.js +70 -139
  86. package/dist/index47.js.map +1 -1
  87. package/dist/index48.js +9 -9
  88. package/dist/index48.js.map +1 -1
  89. package/dist/index49.js +6 -112
  90. package/dist/index49.js.map +1 -1
  91. package/dist/index5.js +1 -1
  92. package/dist/index50.js +3678 -124
  93. package/dist/index50.js.map +1 -1
  94. package/dist/index51.js +48 -131
  95. package/dist/index51.js.map +1 -1
  96. package/dist/index52.js +17 -109
  97. package/dist/index52.js.map +1 -1
  98. package/dist/index53.js +3 -138
  99. package/dist/index53.js.map +1 -1
  100. package/dist/index54.js +10 -7
  101. package/dist/index54.js.map +1 -1
  102. package/dist/index55.js +107 -48
  103. package/dist/index55.js.map +1 -1
  104. package/dist/index56.js +136 -0
  105. package/dist/index56.js.map +1 -0
  106. package/dist/index57.js +137 -0
  107. package/dist/index57.js.map +1 -0
  108. package/dist/index58.js +112 -0
  109. package/dist/index58.js.map +1 -0
  110. package/dist/index59.js +9 -0
  111. package/dist/index59.js.map +1 -0
  112. package/dist/index6.js +1 -1
  113. package/dist/index7.js +1 -1
  114. package/dist/index8.js +20 -2
  115. package/dist/index8.js.map +1 -1
  116. package/dist/index9.js +11 -27
  117. package/dist/index9.js.map +1 -1
  118. package/dist/module.d.ts +43 -4
  119. package/dist/services/keyboardControls.d.ts +11 -1
  120. package/package.json +11 -10
  121. package/src/Game/Object.ts +90 -8
  122. package/src/Game/TransitionManager.ts +75 -0
  123. package/src/Gui/Gui.ts +5 -31
  124. package/src/RpgClientEngine.ts +430 -16
  125. package/src/components/character.ce +212 -11
  126. package/src/components/gui/mobile/index.ts +24 -0
  127. package/src/components/gui/mobile/mobile.ce +95 -0
  128. package/src/components/prebuilt/index.ts +2 -0
  129. package/src/components/prebuilt/light-halo.ce +217 -0
  130. package/src/components/scenes/canvas.ce +12 -2
  131. package/src/components/scenes/draw-map.ce +12 -3
  132. package/src/components/scenes/transition.ce +60 -0
  133. package/src/index.ts +7 -1
  134. package/src/module.ts +66 -2
  135. package/src/services/keyboardControls.ts +14 -2
package/dist/index2.js CHANGED
@@ -1,20 +1,20 @@
1
- import component from './index21.js';
2
- import { inject } from './index19.js';
3
- import { signal, bootstrapCanvas, Howl } from 'canvasengine';
4
- import { WebSocketToken } from './index22.js';
1
+ import component from './index26.js';
2
+ import { inject } from './index6.js';
3
+ import { signal, trigger, bootstrapCanvas, Howl } from 'canvasengine';
4
+ import { WebSocketToken } from './index27.js';
5
5
  import { LoadMapToken } from './index7.js';
6
- import { RpgSound } from './index17.js';
7
- import { RpgResource } from './index18.js';
6
+ import { RpgSound } from './index18.js';
7
+ import { RpgResource } from './index19.js';
8
8
  import { Direction, PredictionController, ModulesToken } from '@rpgjs/common';
9
- import { load } from './index23.js';
10
- import { RpgClientMap } from './index24.js';
9
+ import { load } from './index28.js';
10
+ import { RpgClientMap } from './index29.js';
11
11
  import { RpgGui } from './index9.js';
12
- import { AnimationManager } from './index25.js';
12
+ import { AnimationManager } from './index30.js';
13
+ import { TransitionManager } from './index31.js';
13
14
  import { lastValueFrom } from 'rxjs';
14
15
  import { GlobalConfigToken } from './index8.js';
15
16
  import * as PIXI from 'pixi.js';
16
17
  import { PrebuiltComponentAnimations } from './index12.js';
17
- import { KeyboardControls } from './index20.js';
18
18
 
19
19
  class RpgClientEngine {
20
20
  constructor(context) {
@@ -25,12 +25,17 @@ class RpgClientEngine {
25
25
  this.spritesheets = /* @__PURE__ */ new Map();
26
26
  this.sounds = /* @__PURE__ */ new Map();
27
27
  this.componentAnimations = [];
28
+ this.transitions = [];
28
29
  this.particleSettings = {
29
30
  emitters: []
30
31
  };
31
32
  this.playerIdSignal = signal(null);
32
33
  this.spriteComponentsBehind = signal([]);
33
34
  this.spriteComponentsInFront = signal([]);
35
+ /** ID of the sprite that the camera should follow. null means follow the current player */
36
+ this.cameraFollowTargetId = signal(null);
37
+ /** Trigger for map shake animation */
38
+ this.mapShakeTrigger = trigger();
34
39
  this.predictionEnabled = false;
35
40
  this.SERVER_CORRECTION_THRESHOLD = 30;
36
41
  this.inputFrameCounter = 0;
@@ -42,11 +47,11 @@ class RpgClientEngine {
42
47
  this.PING_INTERVAL_MS = 5e3;
43
48
  // Send ping every 5 seconds
44
49
  this.lastInputTime = 0;
45
- this.webSocket = inject(context, WebSocketToken);
46
- this.guiService = inject(context, RpgGui);
47
- this.loadMapService = inject(context, LoadMapToken);
48
- this.hooks = inject(context, ModulesToken);
49
- this.globalConfig = inject(context, GlobalConfigToken);
50
+ this.webSocket = inject(WebSocketToken);
51
+ this.guiService = inject(RpgGui);
52
+ this.loadMapService = inject(LoadMapToken);
53
+ this.hooks = inject(ModulesToken);
54
+ this.globalConfig = inject(GlobalConfigToken);
50
55
  if (!this.globalConfig) {
51
56
  this.globalConfig = {};
52
57
  }
@@ -100,8 +105,8 @@ class RpgClientEngine {
100
105
  * ```
101
106
  */
102
107
  setKeyboardControls(controlInstance) {
103
- const currentValues = this.context.values["inject:" + KeyboardControls];
104
- this.context.values["inject:" + KeyboardControls] = {
108
+ const currentValues = this.context.values["inject:KeyboardControls"];
109
+ this.context.values["inject:KeyboardControls"] = {
105
110
  ...currentValues,
106
111
  values: /* @__PURE__ */ new Map([["__default__", controlInstance]])
107
112
  };
@@ -128,6 +133,7 @@ class RpgClientEngine {
128
133
  this.hooks.callHooks("client-gui-load", this).subscribe();
129
134
  this.hooks.callHooks("client-particles-load", this).subscribe();
130
135
  this.hooks.callHooks("client-componentAnimations-load", this).subscribe();
136
+ this.hooks.callHooks("client-transitions-load", this).subscribe();
131
137
  this.hooks.callHooks("client-sprite-load", this).subscribe();
132
138
  await lastValueFrom(this.hooks.callHooks("client-engine-onStart", this));
133
139
  window.addEventListener("resize", () => {
@@ -164,6 +170,7 @@ class RpgClientEngine {
164
170
  });
165
171
  this.webSocket.on("changeMap", (data) => {
166
172
  this.sceneMap.reset();
173
+ this.cameraFollowTargetId.set(null);
167
174
  this.loadScene(data.mapId);
168
175
  });
169
176
  this.webSocket.on("showComponentAnimation", (data) => {
@@ -187,6 +194,29 @@ class RpgClientEngine {
187
194
  const { soundId } = data;
188
195
  this.stopSound(soundId);
189
196
  });
197
+ this.webSocket.on("stopAllSounds", () => {
198
+ this.stopAllSounds();
199
+ });
200
+ this.webSocket.on("cameraFollow", (data) => {
201
+ const { targetId, smoothMove } = data;
202
+ this.setCameraFollow(targetId, smoothMove);
203
+ });
204
+ this.webSocket.on("flash", (data) => {
205
+ const { object, type, duration, cycles, alpha, tint } = data;
206
+ const sprite = object ? this.sceneMap.getObjectById(object) : void 0;
207
+ if (sprite && typeof sprite.flash === "function") {
208
+ sprite.flash({ type, duration, cycles, alpha, tint });
209
+ }
210
+ });
211
+ this.webSocket.on("shakeMap", (data) => {
212
+ const { intensity, duration, frequency, direction } = data || {};
213
+ this.mapShakeTrigger.start({
214
+ intensity,
215
+ duration,
216
+ frequency,
217
+ direction
218
+ });
219
+ });
190
220
  this.webSocket.on("open", () => {
191
221
  this.hooks.callHooks("client-engine-onConnected", this, this.socket).subscribe();
192
222
  });
@@ -552,6 +582,64 @@ class RpgClientEngine {
552
582
  console.warn(`Sound with id "${soundId}" not found or cannot be stopped`);
553
583
  }
554
584
  }
585
+ /**
586
+ * Stop all currently playing sounds
587
+ *
588
+ * This method stops all sounds that are currently playing.
589
+ * Useful when changing maps to prevent sound overlap.
590
+ *
591
+ * @example
592
+ * ```ts
593
+ * // Stop all sounds
594
+ * engine.stopAllSounds();
595
+ * ```
596
+ */
597
+ stopAllSounds() {
598
+ this.sounds.forEach((sound) => {
599
+ if (sound && sound.stop) {
600
+ sound.stop();
601
+ }
602
+ });
603
+ }
604
+ /**
605
+ * Set the camera to follow a specific sprite
606
+ *
607
+ * This method changes which sprite the camera viewport should follow.
608
+ * The camera will smoothly animate to the target sprite if smoothMove options are provided.
609
+ *
610
+ * ## Design
611
+ *
612
+ * The camera follow target is stored in a signal that is read by sprite components.
613
+ * Each sprite checks if it should be followed by comparing its ID with the target ID.
614
+ * When smoothMove options are provided, the viewport animation is handled by CanvasEngine's
615
+ * viewport system.
616
+ *
617
+ * @param targetId - The ID of the sprite to follow. Set to null to follow the current player
618
+ * @param smoothMove - Animation options. Can be a boolean (default: true) or an object with time and ease
619
+ * @param smoothMove.time - Duration of the animation in milliseconds (optional)
620
+ * @param smoothMove.ease - Easing function name from https://easings.net (optional)
621
+ *
622
+ * @example
623
+ * ```ts
624
+ * // Follow another player with default smooth animation
625
+ * engine.setCameraFollow(otherPlayerId, true);
626
+ *
627
+ * // Follow an event with custom smooth animation
628
+ * engine.setCameraFollow(eventId, {
629
+ * time: 1000,
630
+ * ease: "easeInOutQuad"
631
+ * });
632
+ *
633
+ * // Follow without animation (instant)
634
+ * engine.setCameraFollow(targetId, false);
635
+ *
636
+ * // Return to following current player
637
+ * engine.setCameraFollow(null);
638
+ * ```
639
+ */
640
+ setCameraFollow(targetId, smoothMove) {
641
+ this.cameraFollowTargetId.set(targetId);
642
+ }
555
643
  addParticle(particle) {
556
644
  this.particleSettings.emitters.push(particle);
557
645
  return particle;
@@ -560,13 +648,38 @@ class RpgClientEngine {
560
648
  * Add a component to render behind sprites
561
649
  * Components added with this method will be displayed with a lower z-index than the sprite
562
650
  *
563
- * @param component - The component to add behind sprites
564
- * @returns The added component
651
+ * Supports multiple formats:
652
+ * 1. Direct component: `ShadowComponent`
653
+ * 2. Configuration object: `{ component: LightHalo, props: {...} }`
654
+ * 3. With dynamic props: `{ component: LightHalo, props: (object) => {...} }`
655
+ * 4. With dependencies: `{ component: HealthBar, dependencies: (object) => [object.hp, object.param.maxHp] }`
656
+ *
657
+ * Components with dependencies will only be displayed when all dependencies are resolved (!= undefined).
658
+ * The object (sprite) is passed to the dependencies function to allow sprite-specific dependency resolution.
659
+ *
660
+ * @param component - The component to add behind sprites, or a configuration object
661
+ * @param component.component - The component function to render
662
+ * @param component.props - Static props object or function that receives the sprite object and returns props
663
+ * @param component.dependencies - Function that receives the sprite object and returns an array of Signals
664
+ * @returns The added component or configuration
565
665
  *
566
666
  * @example
567
667
  * ```ts
568
668
  * // Add a shadow component behind all sprites
569
669
  * engine.addSpriteComponentBehind(ShadowComponent);
670
+ *
671
+ * // Add a component with static props
672
+ * engine.addSpriteComponentBehind({
673
+ * component: LightHalo,
674
+ * props: { radius: 30 }
675
+ * });
676
+ *
677
+ * // Add a component with dynamic props and dependencies
678
+ * engine.addSpriteComponentBehind({
679
+ * component: HealthBar,
680
+ * props: (object) => ({ hp: object.hp(), maxHp: object.param.maxHp() }),
681
+ * dependencies: (object) => [object.hp, object.param.maxHp]
682
+ * });
570
683
  * ```
571
684
  */
572
685
  addSpriteComponentBehind(component) {
@@ -577,13 +690,38 @@ class RpgClientEngine {
577
690
  * Add a component to render in front of sprites
578
691
  * Components added with this method will be displayed with a higher z-index than the sprite
579
692
  *
580
- * @param component - The component to add in front of sprites
581
- * @returns The added component
693
+ * Supports multiple formats:
694
+ * 1. Direct component: `HealthBarComponent`
695
+ * 2. Configuration object: `{ component: StatusIndicator, props: {...} }`
696
+ * 3. With dynamic props: `{ component: HealthBar, props: (object) => {...} }`
697
+ * 4. With dependencies: `{ component: HealthBar, dependencies: (object) => [object.hp, object.param.maxHp] }`
698
+ *
699
+ * Components with dependencies will only be displayed when all dependencies are resolved (!= undefined).
700
+ * The object (sprite) is passed to the dependencies function to allow sprite-specific dependency resolution.
701
+ *
702
+ * @param component - The component to add in front of sprites, or a configuration object
703
+ * @param component.component - The component function to render
704
+ * @param component.props - Static props object or function that receives the sprite object and returns props
705
+ * @param component.dependencies - Function that receives the sprite object and returns an array of Signals
706
+ * @returns The added component or configuration
582
707
  *
583
708
  * @example
584
709
  * ```ts
585
710
  * // Add a health bar component in front of all sprites
586
711
  * engine.addSpriteComponentInFront(HealthBarComponent);
712
+ *
713
+ * // Add a component with static props
714
+ * engine.addSpriteComponentInFront({
715
+ * component: StatusIndicator,
716
+ * props: { type: 'poison' }
717
+ * });
718
+ *
719
+ * // Add a component with dynamic props and dependencies
720
+ * engine.addSpriteComponentInFront({
721
+ * component: HealthBar,
722
+ * props: (object) => ({ hp: object.hp(), maxHp: object.param.maxHp() }),
723
+ * dependencies: (object) => [object.hp, object.param.maxHp]
724
+ * });
587
725
  * ```
588
726
  */
589
727
  addSpriteComponentInFront(component) {
@@ -650,6 +788,179 @@ class RpgClientEngine {
650
788
  }
651
789
  return componentAnimation.instance;
652
790
  }
791
+ /**
792
+ * Add a transition to the engine
793
+ *
794
+ * Transitions are screen effects that can be displayed during scene changes,
795
+ * map loading, or any other moment where a visual transition is needed.
796
+ * They are displayed on top of the entire canvas and can have custom props
797
+ * that can be functions (similar to ComponentAnimation).
798
+ *
799
+ * @param transition - The transition configuration
800
+ * @param transition.id - Unique identifier for the transition
801
+ * @param transition.component - The component function to render
802
+ * @param transition.props - Optional props to pass to the component (can be a function)
803
+ * @returns The added transition configuration
804
+ *
805
+ * @example
806
+ * ```ts
807
+ * // Add a fade transition
808
+ * engine.addTransition({
809
+ * id: 'fade',
810
+ * component: FadeComponent
811
+ * });
812
+ *
813
+ * // Add a transition with props
814
+ * engine.addTransition({
815
+ * id: 'slide',
816
+ * component: SlideComponent,
817
+ * props: { direction: 'left', duration: 500 }
818
+ * });
819
+ *
820
+ * // Add a transition with function props
821
+ * engine.addTransition({
822
+ * id: 'custom',
823
+ * component: CustomTransition,
824
+ * props: (engine) => ({ width: engine.width(), height: engine.height() })
825
+ * });
826
+ * ```
827
+ */
828
+ addTransition(transition) {
829
+ const instance = new TransitionManager();
830
+ this.transitions.push({
831
+ id: transition.id,
832
+ component: transition.component,
833
+ props: transition.props,
834
+ instance,
835
+ current: instance.current
836
+ });
837
+ return transition;
838
+ }
839
+ /**
840
+ * Remove a transition from the engine
841
+ *
842
+ * Removes a transition by its ID. This will not affect any currently
843
+ * running transitions, only prevent new ones from being started.
844
+ *
845
+ * @param id - The unique identifier of the transition to remove
846
+ * @returns true if the transition was found and removed, false otherwise
847
+ *
848
+ * @example
849
+ * ```ts
850
+ * // Remove a transition
851
+ * engine.removeTransition('fade');
852
+ * ```
853
+ */
854
+ removeTransition(id) {
855
+ const index = this.transitions.findIndex((transition) => transition.id === id);
856
+ if (index !== -1) {
857
+ this.transitions.splice(index, 1);
858
+ return true;
859
+ }
860
+ return false;
861
+ }
862
+ /**
863
+ * Modify an existing transition
864
+ *
865
+ * Updates the component or props of an existing transition. This will
866
+ * not affect any currently running transitions, only future ones.
867
+ *
868
+ * @param id - The unique identifier of the transition to modify
869
+ * @param updates - The updates to apply (component and/or props)
870
+ * @returns true if the transition was found and modified, false otherwise
871
+ *
872
+ * @example
873
+ * ```ts
874
+ * // Update transition props
875
+ * engine.modifyTransition('fade', {
876
+ * props: { duration: 2000, color: 'white' }
877
+ * });
878
+ *
879
+ * // Update transition component
880
+ * engine.modifyTransition('fade', {
881
+ * component: NewFadeComponent
882
+ * });
883
+ * ```
884
+ */
885
+ modifyTransition(id, updates) {
886
+ const transition = this.transitions.find((transition2) => transition2.id === id);
887
+ if (!transition) {
888
+ return false;
889
+ }
890
+ if (updates.component !== void 0) {
891
+ transition.component = updates.component;
892
+ }
893
+ if (updates.props !== void 0) {
894
+ transition.props = updates.props;
895
+ }
896
+ return true;
897
+ }
898
+ /**
899
+ * Get a transition by its ID
900
+ *
901
+ * Retrieves the TransitionManager instance for a specific transition,
902
+ * which can be used to start the transition.
903
+ *
904
+ * @param id - The unique identifier of the transition
905
+ * @returns The TransitionManager instance for the transition
906
+ * @throws Error if the transition is not found
907
+ *
908
+ * @example
909
+ * ```ts
910
+ * // Get a transition and start it
911
+ * const fadeTransition = engine.getTransition('fade');
912
+ * fadeTransition.start({ duration: 1000 });
913
+ * ```
914
+ */
915
+ getTransition(id) {
916
+ const transition = this.transitions.find((transition2) => transition2.id === id);
917
+ if (!transition) {
918
+ throw new Error(`Transition with id ${id} not found`);
919
+ }
920
+ return transition.instance;
921
+ }
922
+ /**
923
+ * Start a transition
924
+ *
925
+ * Convenience method to start a transition by its ID. This combines
926
+ * getTransition and start into a single call. The transition will
927
+ * automatically receive an onFinish callback to remove itself when done.
928
+ *
929
+ * @param id - The unique identifier of the transition to start
930
+ * @param props - Additional props to pass to the transition component
931
+ * @returns The created transition object
932
+ *
933
+ * @example
934
+ * ```ts
935
+ * // Start a fade transition
936
+ * engine.startTransition('fade', { duration: 1000, color: 'black' });
937
+ *
938
+ * // Start with onFinish callback
939
+ * engine.startTransition('fade', {
940
+ * duration: 1000,
941
+ * onFinish: () => console.log('Fade complete')
942
+ * });
943
+ * ```
944
+ */
945
+ startTransition(id, props = {}) {
946
+ const transition = this.transitions.find((t) => t.id === id);
947
+ if (!transition) {
948
+ throw new Error(`Transition with id ${id} not found`);
949
+ }
950
+ let baseProps = {};
951
+ if (transition.props) {
952
+ if (typeof transition.props === "function") {
953
+ baseProps = transition.props(this);
954
+ } else {
955
+ baseProps = transition.props;
956
+ }
957
+ }
958
+ const finalProps = {
959
+ ...baseProps,
960
+ ...props
961
+ };
962
+ return transition.instance.start(finalProps);
963
+ }
653
964
  async processInput({ input }) {
654
965
  const timestamp = Date.now();
655
966
  let frame;
@@ -756,6 +1067,61 @@ class RpgClientEngine {
756
1067
  this.frameOffset = 0;
757
1068
  this.inputFrameCounter = 0;
758
1069
  }
1070
+ /**
1071
+ * Trigger a flash animation on a sprite
1072
+ *
1073
+ * This method allows you to trigger a flash effect on any sprite from client-side code.
1074
+ * The flash can be configured with various options including type (alpha, tint, or both),
1075
+ * duration, cycles, and color.
1076
+ *
1077
+ * ## Design
1078
+ *
1079
+ * The flash is applied directly to the sprite object using its flash trigger.
1080
+ * This is useful for client-side visual feedback, UI interactions, or local effects
1081
+ * that don't need to be synchronized with the server.
1082
+ *
1083
+ * @param spriteId - The ID of the sprite to flash. If not provided, flashes the current player
1084
+ * @param options - Flash configuration options
1085
+ * @param options.type - Type of flash effect: 'alpha' (opacity), 'tint' (color), or 'both' (default: 'alpha')
1086
+ * @param options.duration - Duration of the flash animation in milliseconds (default: 300)
1087
+ * @param options.cycles - Number of flash cycles (flash on/off) (default: 1)
1088
+ * @param options.alpha - Alpha value when flashing, from 0 to 1 (default: 0.3)
1089
+ * @param options.tint - Tint color when flashing as hex value or color name (default: 0xffffff - white)
1090
+ *
1091
+ * @example
1092
+ * ```ts
1093
+ * // Flash the current player with default settings
1094
+ * engine.flash();
1095
+ *
1096
+ * // Flash a specific sprite with red tint
1097
+ * engine.flash('sprite-id', { type: 'tint', tint: 0xff0000 });
1098
+ *
1099
+ * // Flash with both alpha and tint for dramatic effect
1100
+ * engine.flash(undefined, {
1101
+ * type: 'both',
1102
+ * alpha: 0.5,
1103
+ * tint: 0xff0000,
1104
+ * duration: 200,
1105
+ * cycles: 2
1106
+ * });
1107
+ *
1108
+ * // Quick damage flash on current player
1109
+ * engine.flash(undefined, {
1110
+ * type: 'tint',
1111
+ * tint: 'red',
1112
+ * duration: 150,
1113
+ * cycles: 1
1114
+ * });
1115
+ * ```
1116
+ */
1117
+ flash(spriteId, options) {
1118
+ const targetId = spriteId || this.playerId;
1119
+ if (!targetId) return;
1120
+ const sprite = this.sceneMap.getObjectById(targetId);
1121
+ if (sprite && typeof sprite.flash === "function") {
1122
+ sprite.flash(options);
1123
+ }
1124
+ }
759
1125
  applyServerAck(ack) {
760
1126
  if (this.predictionEnabled && this.prediction) {
761
1127
  this.prediction.applyServerAck({