@rpgjs/client 5.0.0-beta.10 → 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 (154) hide show
  1. package/CHANGELOG.md +21 -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 +98 -0
  19. package/dist/Game/ProjectileManager.js +196 -0
  20. package/dist/Game/ProjectileManager.js.map +1 -0
  21. package/dist/Game/ProjectileManager.spec.d.ts +1 -0
  22. package/dist/RpgClient.d.ts +117 -13
  23. package/dist/RpgClientEngine.d.ts +82 -4
  24. package/dist/RpgClientEngine.js +296 -51
  25. package/dist/RpgClientEngine.js.map +1 -1
  26. package/dist/components/animations/fx.ce.js +58 -0
  27. package/dist/components/animations/fx.ce.js.map +1 -0
  28. package/dist/components/animations/hit.ce.js.map +1 -1
  29. package/dist/components/animations/index.d.ts +1 -0
  30. package/dist/components/animations/index.js +3 -1
  31. package/dist/components/animations/index.js.map +1 -1
  32. package/dist/components/character.ce.js +140 -40
  33. package/dist/components/character.ce.js.map +1 -1
  34. package/dist/components/dynamics/bar.ce.js +4 -3
  35. package/dist/components/dynamics/bar.ce.js.map +1 -1
  36. package/dist/components/dynamics/image.ce.js +2 -1
  37. package/dist/components/dynamics/image.ce.js.map +1 -1
  38. package/dist/components/dynamics/shape.ce.js +3 -2
  39. package/dist/components/dynamics/shape.ce.js.map +1 -1
  40. package/dist/components/dynamics/text.ce.js +9 -8
  41. package/dist/components/dynamics/text.ce.js.map +1 -1
  42. package/dist/components/gui/dialogbox/index.ce.js +3 -2
  43. package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
  44. package/dist/components/gui/gameover.ce.js +3 -2
  45. package/dist/components/gui/gameover.ce.js.map +1 -1
  46. package/dist/components/gui/hud/hud.ce.js.map +1 -1
  47. package/dist/components/gui/menu/equip-menu.ce.js +2 -1
  48. package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
  49. package/dist/components/gui/menu/exit-menu.ce.js +2 -1
  50. package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
  51. package/dist/components/gui/menu/items-menu.ce.js +3 -2
  52. package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
  53. package/dist/components/gui/menu/main-menu.ce.js +3 -2
  54. package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
  55. package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
  56. package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
  57. package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
  58. package/dist/components/gui/notification/notification.ce.js.map +1 -1
  59. package/dist/components/gui/save-load.ce.js +2 -1
  60. package/dist/components/gui/save-load.ce.js.map +1 -1
  61. package/dist/components/gui/shop/shop.ce.js +3 -2
  62. package/dist/components/gui/shop/shop.ce.js.map +1 -1
  63. package/dist/components/gui/title-screen.ce.js +3 -2
  64. package/dist/components/gui/title-screen.ce.js.map +1 -1
  65. package/dist/components/index.d.ts +2 -1
  66. package/dist/components/index.js +1 -0
  67. package/dist/components/player-components.ce.js +11 -10
  68. package/dist/components/player-components.ce.js.map +1 -1
  69. package/dist/components/prebuilt/hp-bar.ce.js +4 -3
  70. package/dist/components/prebuilt/hp-bar.ce.js.map +1 -1
  71. package/dist/components/prebuilt/light-halo.ce.js +2 -1
  72. package/dist/components/prebuilt/light-halo.ce.js.map +1 -1
  73. package/dist/components/scenes/canvas.ce.js +12 -4
  74. package/dist/components/scenes/canvas.ce.js.map +1 -1
  75. package/dist/components/scenes/draw-map.ce.js +6 -3
  76. package/dist/components/scenes/draw-map.ce.js.map +1 -1
  77. package/dist/components/scenes/event-layer.ce.js.map +1 -1
  78. package/dist/index.d.ts +4 -0
  79. package/dist/index.js +10 -5
  80. package/dist/module.js +18 -0
  81. package/dist/module.js.map +1 -1
  82. package/dist/services/actionInput.d.ts +14 -0
  83. package/dist/services/actionInput.js +59 -0
  84. package/dist/services/actionInput.js.map +1 -0
  85. package/dist/services/actionInput.spec.d.ts +1 -0
  86. package/dist/services/mmorpg-connection.d.ts +5 -0
  87. package/dist/services/mmorpg-connection.js +50 -0
  88. package/dist/services/mmorpg-connection.js.map +1 -0
  89. package/dist/services/mmorpg-connection.spec.d.ts +1 -0
  90. package/dist/services/mmorpg.d.ts +10 -4
  91. package/dist/services/mmorpg.js +48 -30
  92. package/dist/services/mmorpg.js.map +1 -1
  93. package/dist/services/pointerContext.d.ts +11 -0
  94. package/dist/services/pointerContext.js +48 -0
  95. package/dist/services/pointerContext.js.map +1 -0
  96. package/dist/services/pointerContext.spec.d.ts +1 -0
  97. package/dist/services/standalone-message.d.ts +1 -0
  98. package/dist/services/standalone-message.js +9 -0
  99. package/dist/services/standalone-message.js.map +1 -0
  100. package/dist/services/standalone.d.ts +3 -1
  101. package/dist/services/standalone.js +34 -15
  102. package/dist/services/standalone.js.map +1 -1
  103. package/dist/services/standalone.spec.d.ts +1 -0
  104. package/dist/utils/mapId.d.ts +1 -0
  105. package/dist/utils/mapId.js +6 -0
  106. package/dist/utils/mapId.js.map +1 -0
  107. package/package.json +7 -7
  108. package/src/Game/AnimationManager.ts +4 -0
  109. package/src/Game/ClientVisuals.spec.ts +56 -0
  110. package/src/Game/ClientVisuals.ts +184 -0
  111. package/src/Game/EventComponentResolver.spec.ts +84 -0
  112. package/src/Game/EventComponentResolver.ts +74 -0
  113. package/src/Game/Map.ts +10 -0
  114. package/src/Game/Object.spec.ts +46 -0
  115. package/src/Game/Object.ts +2 -2
  116. package/src/Game/ProjectileManager.spec.ts +449 -0
  117. package/src/Game/ProjectileManager.ts +346 -0
  118. package/src/RpgClient.ts +130 -15
  119. package/src/RpgClientEngine.ts +405 -69
  120. package/src/components/animations/fx.ce +101 -0
  121. package/src/components/animations/index.ts +4 -2
  122. package/src/components/character.ce +185 -40
  123. package/src/components/dynamics/bar.ce +4 -3
  124. package/src/components/dynamics/image.ce +2 -1
  125. package/src/components/dynamics/shape.ce +3 -2
  126. package/src/components/dynamics/text.ce +9 -8
  127. package/src/components/gui/dialogbox/index.ce +3 -2
  128. package/src/components/gui/gameover.ce +2 -1
  129. package/src/components/gui/menu/equip-menu.ce +2 -1
  130. package/src/components/gui/menu/exit-menu.ce +2 -1
  131. package/src/components/gui/menu/items-menu.ce +3 -2
  132. package/src/components/gui/menu/main-menu.ce +2 -1
  133. package/src/components/gui/save-load.ce +2 -1
  134. package/src/components/gui/shop/shop.ce +3 -2
  135. package/src/components/gui/title-screen.ce +2 -1
  136. package/src/components/index.ts +2 -1
  137. package/src/components/player-components.ce +11 -10
  138. package/src/components/prebuilt/hp-bar.ce +4 -3
  139. package/src/components/prebuilt/light-halo.ce +2 -2
  140. package/src/components/scenes/canvas.ce +10 -2
  141. package/src/components/scenes/draw-map.ce +17 -3
  142. package/src/index.ts +4 -0
  143. package/src/module.ts +24 -0
  144. package/src/services/actionInput.spec.ts +155 -0
  145. package/src/services/actionInput.ts +120 -0
  146. package/src/services/mmorpg-connection.spec.ts +99 -0
  147. package/src/services/mmorpg-connection.ts +69 -0
  148. package/src/services/mmorpg.ts +60 -34
  149. package/src/services/pointerContext.spec.ts +36 -0
  150. package/src/services/pointerContext.ts +84 -0
  151. package/src/services/standalone-message.ts +7 -0
  152. package/src/services/standalone.spec.ts +34 -0
  153. package/src/services/standalone.ts +42 -12
  154. package/src/utils/mapId.ts +2 -0
@@ -0,0 +1,101 @@
1
+ <Fx
2
+ name={name}
3
+ preset={preset}
4
+ trigger={trigger}
5
+ autostart={autostart}
6
+ loop={loop}
7
+ enabled={enabled}
8
+ x={x}
9
+ y={y}
10
+ rotation={rotation}
11
+ scale={scale}
12
+ alpha={alpha}
13
+ timeScale={timeScale}
14
+ maxParticles={maxParticles}
15
+ preload={preload}
16
+ missingTexture={missingTexture}
17
+ zIndex={zIndex}
18
+ onStart={onStart}
19
+ onComplete={finish}
20
+ onParticleSpawn={onParticleSpawn}
21
+ />
22
+
23
+ <script>
24
+ import { tick } from "canvasengine";
25
+ import { Fx } from "@canvasengine/presets";
26
+
27
+ const {
28
+ name,
29
+ preset,
30
+ trigger,
31
+ onFinish,
32
+ onStart,
33
+ onComplete,
34
+ onParticleSpawn,
35
+ displayDuration,
36
+ duration,
37
+ autostart,
38
+ loop,
39
+ enabled,
40
+ x,
41
+ y,
42
+ rotation,
43
+ scale,
44
+ alpha,
45
+ timeScale,
46
+ maxParticles,
47
+ preload,
48
+ missingTexture,
49
+ zIndex,
50
+ } = defineProps({
51
+ autostart: {
52
+ default: true,
53
+ },
54
+ loop: {
55
+ default: false,
56
+ },
57
+ enabled: {
58
+ default: true,
59
+ },
60
+ rotation: {
61
+ default: 0,
62
+ },
63
+ scale: {
64
+ default: 1,
65
+ },
66
+ alpha: {
67
+ default: 1,
68
+ },
69
+ timeScale: {
70
+ default: 1,
71
+ },
72
+ maxParticles: {
73
+ default: 600,
74
+ },
75
+ preload: {
76
+ default: true,
77
+ },
78
+ missingTexture: {
79
+ default: "shape",
80
+ },
81
+ });
82
+
83
+ let elapsedTime = 0;
84
+ let finished = false;
85
+
86
+ function finish(instance) {
87
+ if (finished) return;
88
+ finished = true;
89
+ onComplete?.(instance);
90
+ onFinish?.(instance);
91
+ }
92
+
93
+ tick(({ deltaTime }) => {
94
+ const maxDuration = displayDuration?.() ?? (loop() ? duration?.() : undefined);
95
+ if (!maxDuration || finished) return;
96
+ elapsedTime += deltaTime;
97
+ if (elapsedTime >= maxDuration) {
98
+ finish();
99
+ }
100
+ });
101
+ </script>
@@ -1,7 +1,9 @@
1
1
  import Hit from "./hit.ce";
2
2
  import Animation from "./animation.ce";
3
+ import Fx from "./fx.ce";
3
4
 
4
5
  export const PrebuiltComponentAnimations = {
5
6
  Hit,
6
- Animation
7
- }
7
+ Animation,
8
+ Fx
9
+ }
@@ -1,14 +1,14 @@
1
1
  <Container x={smoothX} y={smoothY} zIndex={z} viewportFollow={shouldFollowCamera} controls onBeforeDestroy visible>
2
2
  @for (compConfig of normalizedComponentsBehind) {
3
3
  <Container>
4
- <compConfig.component object ...compConfig.props />
4
+ <compConfig.component object={sprite} ...compConfig.props />
5
5
  </Container>
6
6
  }
7
- <PlayerComponents object position="bottom" graphicBounds />
8
- <PlayerComponents object position="left" graphicBounds />
7
+ <PlayerComponents object={sprite} position="bottom" graphicBounds />
8
+ <PlayerComponents object={sprite} position="left" graphicBounds />
9
9
  <Particle emit={emitParticleTrigger} settings={particleSettings} zIndex={1000} name={particleName} />
10
10
  <Container>
11
- @for (graphicObj of graphicsSignals) {
11
+ @for (graphicObj of renderedGraphics) {
12
12
  <Container scale={graphicScale(graphicObj)}>
13
13
  <Sprite
14
14
  sheet={sheet(graphicObj)}
@@ -20,19 +20,24 @@
20
20
  />
21
21
  </Container>
22
22
  }
23
+ @for (eventComponent of resolvedEventComponents) {
24
+ <Container dependencies={eventComponent.dependencies}>
25
+ <eventComponent.component ...eventComponent.props />
26
+ </Container>
27
+ }
23
28
  </Container>
24
- <PlayerComponents object position="center" graphicBounds />
25
- <PlayerComponents object position="right" graphicBounds />
26
- <PlayerComponents object position="top" graphicBounds />
29
+ <PlayerComponents object={sprite} position="center" graphicBounds />
30
+ <PlayerComponents object={sprite} position="right" graphicBounds />
31
+ <PlayerComponents object={sprite} position="top" graphicBounds />
27
32
  @for (compConfig of normalizedComponentsInFront) {
28
33
  <Container dependencies={compConfig.dependencies}>
29
- <compConfig.component object ...compConfig.props />
34
+ <compConfig.component object={sprite} ...compConfig.props />
30
35
  </Container>
31
36
  }
32
37
  @for (attachedGui of attachedGuis) {
33
38
  @if (shouldDisplayAttachedGui) {
34
39
  <Container>
35
- <attachedGui.component ...attachedGui.data() dependencies={attachedGui.dependencies} object={object} onFinish={(data) => {
40
+ <attachedGui.component ...attachedGui.data() dependencies={attachedGui.dependencies} object={sprite} onFinish={(data) => {
36
41
  onAttachedGuiFinish(attachedGui, data)
37
42
  }} onInteraction={(name, data) => {
38
43
  onAttachedGuiInteraction(attachedGui, name, data)
@@ -52,12 +57,20 @@
52
57
  import { RpgClientEngine } from "../RpgClientEngine";
53
58
  import { inject } from "../core/inject";
54
59
  import { Direction, Animation } from "@rpgjs/common";
60
+ import { normalizeEventComponent } from "../Game/EventComponentResolver";
55
61
  import Hit from "./effects/hit.ce";
56
62
  import PlayerComponents from "./player-components.ce";
57
63
  import { RpgGui } from "../Gui/Gui";
58
64
  import { getCanMoveValue } from "../utils/readPropValue";
65
+ import {
66
+ getKeyboardControlBind,
67
+ keyboardEventMatchesBind,
68
+ resolveKeyboardActionInput,
69
+ resolveKeyboardDirectionInput,
70
+ } from "../services/actionInput";
59
71
 
60
72
  const { object, id } = defineProps();
73
+ const sprite = object();
61
74
 
62
75
  const client = inject(RpgClientEngine);
63
76
  const hooks = inject(ModulesToken);
@@ -71,9 +84,9 @@
71
84
  const playerId = client.playerIdSignal();
72
85
  const currentPlayer = playerId ? client.sceneMap?.players?.()?.[playerId] : undefined;
73
86
  return readProp(id) === playerId
74
- || readProp(object?.id) === playerId
75
- || object === currentPlayer
76
- || object === client.sceneMap?.getCurrentPlayer?.();
87
+ || readProp(sprite?.id) === playerId
88
+ || sprite === currentPlayer
89
+ || sprite === client.sceneMap?.getCurrentPlayer?.();
77
90
  };
78
91
  const isMe = computed(isCurrentPlayer);
79
92
  const shadowsEnabled = computed(() => {
@@ -150,8 +163,8 @@
150
163
  // The computed will be created in the template when needed
151
164
  return {
152
165
  component: componentRef,
153
- props: typeof propsValue === 'function' ? propsValue(object) : propsValue || {},
154
- dependencies: dependenciesFn ? dependenciesFn(object) : []
166
+ props: typeof propsValue === 'function' ? propsValue(sprite) : propsValue || {},
167
+ dependencies: dependenciesFn ? dependenciesFn(sprite) : []
155
168
  };
156
169
  };
157
170
 
@@ -211,6 +224,18 @@
211
224
  const normalizedComponentsInFront = computed(() => {
212
225
  return normalizeComponents(componentsInFront());
213
226
  });
227
+
228
+ const isEventSprite = () => {
229
+ return typeof sprite?.isEvent === 'function'
230
+ ? sprite.isEvent()
231
+ : sprite?._type === 'event';
232
+ };
233
+
234
+ const resolvedEventComponents = computed(() => {
235
+ if (!isEventSprite()) return [];
236
+ const eventComponent = normalizeEventComponent(client.resolveEventComponent(sprite), sprite);
237
+ return eventComponent ? [eventComponent] : [];
238
+ });
214
239
 
215
240
  /**
216
241
  * Determine if the camera should follow this sprite
@@ -259,7 +284,13 @@
259
284
  isConnected,
260
285
  graphicsSignals,
261
286
  flashTrigger
262
- } = object;
287
+ } = sprite;
288
+
289
+ const renderedGraphics = computed(() => {
290
+ const eventComponent = resolvedEventComponents()[0];
291
+ if (eventComponent && !eventComponent.renderGraphic) return [];
292
+ return graphicsSignals();
293
+ });
263
294
 
264
295
  /**
265
296
  * Flash configuration signals for dynamic options
@@ -304,11 +335,92 @@
304
335
 
305
336
  const particleSettings = client.particleSettings;
306
337
 
307
- const canControls = () => isMe() && getCanMoveValue(object)
338
+ const canControls = () => isMe() && getCanMoveValue(sprite)
308
339
  const keyboardControls = client.globalConfig.keyboardControls;
340
+ const activeDirectionKeys = new Map();
341
+
342
+ const resolveHeldDirection = () => {
343
+ const directions = Array.from(activeDirectionKeys.values());
344
+ return directions[directions.length - 1];
345
+ };
346
+
347
+ const resolveSpriteDirection = () => {
348
+ const heldDirection = resolveHeldDirection();
349
+ if (heldDirection) return heldDirection;
350
+ if (typeof sprite.getDirection === 'function') return sprite.getDirection();
351
+ if (typeof sprite.direction === 'function') return sprite.direction();
352
+ return direction();
353
+ };
354
+
355
+ const withCurrentDirection = (payload) => {
356
+ if (payload.action !== 'action') return payload;
357
+ const data = payload.data && typeof payload.data === 'object'
358
+ ? { ...payload.data }
359
+ : {};
360
+ return {
361
+ ...payload,
362
+ data: {
363
+ ...data,
364
+ direction: data.direction ?? resolveSpriteDirection(),
365
+ },
366
+ };
367
+ };
368
+
369
+ const resolveCurrentActionInput = () =>
370
+ withCurrentDirection(
371
+ resolveKeyboardActionInput(keyboardControls.action, client, sprite)
372
+ );
373
+
374
+ const playPredictedWalkAnimation = () => {
375
+ if (sprite.animationFixed) return;
376
+ if (sprite.animationIsPlaying && sprite.animationIsPlaying()) return;
377
+ realAnimationName.set('walk');
378
+ };
379
+
380
+ const resumeHeldDirectionWalkAnimation = () => {
381
+ if (!isCurrentPlayer()) return false;
382
+ if (activeDirectionKeys.size === 0) return false;
383
+ if (!canControls()) return false;
384
+ if (sprite.animationFixed) return false;
385
+ if (sprite.animationIsPlaying && sprite.animationIsPlaying()) return false;
386
+ realAnimationName.set('walk');
387
+ return true;
388
+ };
389
+
390
+ const processMovementInput = (input) => {
391
+ if (!canControls()) return;
392
+ client.processInput({ input });
393
+ playPredictedWalkAnimation();
394
+ };
395
+
396
+ const actionBind = () => getKeyboardControlBind(keyboardControls.action);
397
+ const keyboardEventId = (event) => `${event.keyCode}:${event.code}:${event.key}`;
398
+
399
+ const handleNativeActionWhileMoving = (event) => {
400
+ const inputDirection = resolveKeyboardDirectionInput(event, keyboardControls);
401
+ if (inputDirection) {
402
+ const keyId = keyboardEventId(event);
403
+ if (event.type === 'keydown') {
404
+ activeDirectionKeys.delete(keyId);
405
+ activeDirectionKeys.set(keyId, inputDirection);
406
+ resumeHeldDirectionWalkAnimation();
407
+ }
408
+ else {
409
+ activeDirectionKeys.delete(keyId);
410
+ }
411
+ }
412
+
413
+ if (!isCurrentPlayer()) return;
414
+ if (event.type !== 'keydown' || event.repeat) return;
415
+ if (activeDirectionKeys.size === 0) return;
416
+ if (!keyboardEventMatchesBind(event, actionBind())) return;
417
+ if (!canControls()) return;
418
+
419
+ client.processAction(resolveCurrentActionInput());
420
+ };
309
421
 
310
422
  const visible = computed(() => {
311
- if (object.isEvent()) {
423
+ if (sprite.isEvent()) {
312
424
  return true
313
425
  }
314
426
  return isConnected()
@@ -319,35 +431,35 @@
319
431
  repeat: true,
320
432
  bind: keyboardControls.down,
321
433
  keyDown() {
322
- if (canControls()) client.processInput({ input: Direction.Down })
434
+ processMovementInput(Direction.Down)
323
435
  },
324
436
  },
325
437
  up: {
326
438
  repeat: true,
327
439
  bind: keyboardControls.up,
328
440
  keyDown() {
329
- if (canControls()) client.processInput({ input: Direction.Up })
441
+ processMovementInput(Direction.Up)
330
442
  },
331
443
  },
332
444
  left: {
333
445
  repeat: true,
334
446
  bind: keyboardControls.left,
335
447
  keyDown() {
336
- if (canControls()) client.processInput({ input: Direction.Left })
448
+ processMovementInput(Direction.Left)
337
449
  },
338
450
  },
339
451
  right: {
340
452
  repeat: true,
341
453
  bind: keyboardControls.right,
342
454
  keyDown() {
343
- if (canControls()) client.processInput({ input: Direction.Right })
455
+ processMovementInput(Direction.Right)
344
456
  },
345
457
  },
346
458
  action: {
347
- bind: keyboardControls.action,
459
+ bind: getKeyboardControlBind(keyboardControls.action),
348
460
  keyDown() {
349
461
  if (canControls()) {
350
- client.processAction({ action: 'action' })
462
+ client.processAction(resolveCurrentActionInput())
351
463
  }
352
464
  },
353
465
  },
@@ -373,7 +485,7 @@
373
485
  });
374
486
 
375
487
  const z = computed(() => {
376
- return object.y() + object.z()
488
+ return sprite.y() + sprite.z()
377
489
  });
378
490
 
379
491
  const realAnimationName = signal(animationName());
@@ -646,6 +758,10 @@
646
758
  const graphicBounds = computed(() => {
647
759
  const box = hitbox();
648
760
  const fallback = hitboxBounds();
761
+ const customEventComponent = resolvedEventComponents()[0];
762
+ if (customEventComponent && !customEventComponent.renderGraphic) {
763
+ return fallback;
764
+ }
649
765
  const dimensions = imageDimensions();
650
766
  const graphics = graphicsSignals();
651
767
  let bounds = null;
@@ -734,8 +850,8 @@
734
850
  let beforeRemovePromise = null;
735
851
  let beforeRemoveTransitionValue = null;
736
852
  const resolveRemoveContext = () => {
737
- if (!object._removeTransition) return null;
738
- const value = object._removeTransition();
853
+ if (!sprite._removeTransition) return null;
854
+ const value = sprite._removeTransition();
739
855
  if (!value || typeof value !== 'string') return null;
740
856
  try {
741
857
  const context = JSON.parse(value);
@@ -764,37 +880,57 @@
764
880
  }
765
881
  beforeRemoveTransitionValue = context.__transitionValue;
766
882
  beforeRemovePromise = withTimeout(
767
- lastValueFrom(hooks.callHooks("client-sprite-onBeforeRemove", object, context)),
883
+ lastValueFrom(hooks.callHooks("client-sprite-onBeforeRemove", sprite, context)),
768
884
  context.timeoutMs
769
885
  );
770
886
  return beforeRemovePromise;
771
887
  };
772
888
 
773
- const removeTransitionSubscription = object._removeTransition?.observable?.subscribe(() => {
889
+ const removeTransitionSubscription = sprite._removeTransition?.observable?.subscribe(() => {
774
890
  if (resolveRemoveContext()) {
775
891
  runBeforeRemove();
776
892
  }
777
893
  });
778
894
 
779
895
  const animationMovementSubscription = combineLatest([animationChange$, moving$]).subscribe(([[prev, curr], isMoving]) => {
896
+ const isMovementAnimation = movementAnimations.includes(curr);
897
+ const isTemporaryAnimationPlaying =
898
+ sprite.animationIsPlaying && sprite.animationIsPlaying();
899
+
900
+ if (sprite.animationFixed && isMovementAnimation && isTemporaryAnimationPlaying) {
901
+ return;
902
+ }
903
+
780
904
  if (curr == 'stand' && !isMoving) {
781
- realAnimationName.set(curr);
905
+ if (!resumeHeldDirectionWalkAnimation()) {
906
+ realAnimationName.set(curr);
907
+ }
782
908
  }
783
909
  else if (curr == 'walk' && isMoving) {
784
910
  realAnimationName.set(curr);
785
911
  }
786
- else if (!movementAnimations.includes(curr)) {
912
+ else if (!isMovementAnimation) {
787
913
  realAnimationName.set(curr);
788
914
  }
789
- if (!isMoving && object.animationIsPlaying && object.animationIsPlaying()) {
790
- if (movementAnimations.includes(curr)) {
791
- if (typeof object.resetAnimationState === 'function') {
792
- object.resetAnimationState();
915
+ if (!isMoving && isTemporaryAnimationPlaying) {
916
+ if (isMovementAnimation) {
917
+ if (typeof sprite.resetAnimationState === 'function') {
918
+ sprite.resetAnimationState();
793
919
  }
794
920
  }
795
921
  }
796
922
  });
797
923
 
924
+ const resumeWalkSubscriptions = [
925
+ sprite._canMove,
926
+ sprite._animationFixed,
927
+ sprite.animationIsPlaying,
928
+ ]
929
+ .filter(signal => signal?.observable)
930
+ .map(signal => signal.observable.subscribe(() => {
931
+ resumeHeldDirectionWalkAnimation();
932
+ }));
933
+
798
934
  /**
799
935
  * Cleanup subscriptions and call hooks before sprite destruction.
800
936
  *
@@ -806,7 +942,7 @@
806
942
  * await onBeforeDestroy();
807
943
  */
808
944
  const waitForTemporaryAnimationEnd = (maxDuration = 1200) => {
809
- if (!object.animationIsPlaying || !object.animationIsPlaying()) {
945
+ if (!sprite.animationIsPlaying || !sprite.animationIsPlaying()) {
810
946
  return Promise.resolve();
811
947
  }
812
948
 
@@ -822,7 +958,7 @@
822
958
  resolve();
823
959
  };
824
960
  timeout = setTimeout(finish, maxDuration);
825
- subscription = object.animationIsPlaying.observable.subscribe((isPlaying) => {
961
+ subscription = sprite.animationIsPlaying.observable.subscribe((isPlaying) => {
826
962
  if (!isPlaying) finish();
827
963
  });
828
964
  if (finished) subscription.unsubscribe();
@@ -832,17 +968,26 @@
832
968
  const onBeforeDestroy = async () => {
833
969
  await runBeforeRemove();
834
970
  await waitForTemporaryAnimationEnd();
971
+ if (typeof document !== 'undefined') {
972
+ document.removeEventListener('keydown', handleNativeActionWhileMoving);
973
+ document.removeEventListener('keyup', handleNativeActionWhileMoving);
974
+ }
835
975
  removeTransitionSubscription?.unsubscribe();
836
976
  animationMovementSubscription.unsubscribe();
977
+ resumeWalkSubscriptions.forEach(subscription => subscription.unsubscribe());
837
978
  xSubscription.unsubscribe();
838
979
  ySubscription.unsubscribe();
839
- await lastValueFrom(hooks.callHooks("client-sprite-onDestroy", object))
840
- await lastValueFrom(hooks.callHooks("client-sceneMap-onRemoveSprite", client.sceneMap, object))
980
+ await lastValueFrom(hooks.callHooks("client-sprite-onDestroy", sprite))
981
+ await lastValueFrom(hooks.callHooks("client-sceneMap-onRemoveSprite", client.sceneMap, sprite))
841
982
  }
842
983
 
843
984
  mount((element) => {
844
- hooks.callHooks("client-sprite-onAdd", object).subscribe()
845
- hooks.callHooks("client-sceneMap-onAddSprite", client.sceneMap, object).subscribe()
985
+ if (typeof document !== 'undefined') {
986
+ document.addEventListener('keydown', handleNativeActionWhileMoving);
987
+ document.addEventListener('keyup', handleNativeActionWhileMoving);
988
+ }
989
+ hooks.callHooks("client-sprite-onAdd", sprite).subscribe()
990
+ hooks.callHooks("client-sceneMap-onAddSprite", client.sceneMap, sprite).subscribe()
846
991
  effect(() => {
847
992
  if (isCurrentPlayer()) {
848
993
  client.setKeyboardControls(element.directives.controls)
@@ -10,17 +10,18 @@ import { computed } from "canvasengine";
10
10
  import { resolveDynamicValue } from "./parse-value";
11
11
 
12
12
  const { object, current, max, style, text } = defineProps();
13
+ const sprite = object();
13
14
 
14
15
  const read = (prop, fallback) => prop ? prop() : fallback;
15
16
 
16
17
  const toNumber = (value, fallback = 0) => {
17
- const resolved = resolveDynamicValue(value, object);
18
+ const resolved = resolveDynamicValue(value, sprite);
18
19
  const num = typeof resolved === 'number' ? resolved : parseFloat(resolved);
19
20
  return Number.isFinite(num) ? num : fallback;
20
21
  };
21
22
 
22
23
  const toColor = (value, fallback) => {
23
- const resolved = resolveDynamicValue(value, object);
24
+ const resolved = resolveDynamicValue(value, sprite);
24
25
  if (typeof resolved === 'number') return resolved;
25
26
  if (typeof resolved === 'string' && resolved.startsWith('#')) {
26
27
  return parseInt(resolved.slice(1), 16);
@@ -57,7 +58,7 @@ const labelText = computed(() => {
57
58
  .replace(/\{\$max\}/g, String(maxValue()))
58
59
  .replace(/\{\$percent\}/g, String(Math.round(percent() * 100)));
59
60
 
60
- return String(resolveDynamicValue(value, object) ?? '');
61
+ return String(resolveDynamicValue(value, sprite) ?? '');
61
62
  });
62
63
  const labelSize = computed(() => toNumber(config().fontSize, 10));
63
64
  const hasLabel = computed(() => labelText().length > 0);
@@ -7,10 +7,11 @@ import { inject } from "../../core/inject";
7
7
  import { resolveDynamicValue } from "./parse-value";
8
8
 
9
9
  const { object, value } = defineProps();
10
+ const sprite = object();
10
11
  const client = inject(RpgClientEngine);
11
12
 
12
13
  const sheet = computed(() => {
13
- const id = resolveDynamicValue(value?.(), object);
14
+ const id = resolveDynamicValue(value?.(), sprite);
14
15
  if (!id) return null;
15
16
  return {
16
17
  definition: client.getSpriteSheet(id),
@@ -9,17 +9,18 @@ import { getShapeBox, translatePolygonPoints } from "./shape-utils";
9
9
 
10
10
  const props = defineProps();
11
11
  const { object } = props;
12
+ const sprite = object();
12
13
 
13
14
  const read = (prop, fallback) => prop ? prop() : fallback;
14
15
 
15
16
  const toNumber = (value, fallback = 0) => {
16
- const resolved = resolveDynamicValue(value, object);
17
+ const resolved = resolveDynamicValue(value, sprite);
17
18
  const num = typeof resolved === 'number' ? resolved : parseFloat(resolved);
18
19
  return Number.isFinite(num) ? num : fallback;
19
20
  };
20
21
 
21
22
  const toColor = (value, fallback) => {
22
- const resolved = resolveDynamicValue(value, object);
23
+ const resolved = resolveDynamicValue(value, sprite);
23
24
  if (typeof resolved === 'number') return resolved;
24
25
  if (typeof resolved === 'string' && resolved.startsWith('#')) {
25
26
  return parseInt(resolved.slice(1), 16);
@@ -5,11 +5,12 @@ import { computed } from "canvasengine";
5
5
  import { resolveDynamicValue } from "./parse-value";
6
6
 
7
7
  const { object, value, style } = defineProps();
8
+ const sprite = object();
8
9
 
9
10
  const read = (prop, fallback) => prop ? prop() : fallback;
10
11
 
11
12
  const parseNumericStyleValue = (value, object) => {
12
- value = resolveDynamicValue(value, object);
13
+ value = resolveDynamicValue(value, sprite);
13
14
  if (value === undefined || value === null) return undefined;
14
15
  if (typeof value === 'number') return value;
15
16
 
@@ -22,15 +23,15 @@ const getTextStyle = (style) => {
22
23
  const textStyle = {};
23
24
 
24
25
  if (style.fontStyle !== undefined) {
25
- textStyle.fontStyle = resolveDynamicValue(style.fontStyle, object);
26
+ textStyle.fontStyle = resolveDynamicValue(style.fontStyle, sprite);
26
27
  }
27
28
 
28
29
  if (style.fontWeight !== undefined) {
29
- textStyle.fontWeight = resolveDynamicValue(style.fontWeight, object);
30
+ textStyle.fontWeight = resolveDynamicValue(style.fontWeight, sprite);
30
31
  }
31
32
 
32
33
  if (style.stroke !== undefined) {
33
- textStyle.stroke = resolveDynamicValue(style.stroke, object);
34
+ textStyle.stroke = resolveDynamicValue(style.stroke, sprite);
34
35
  }
35
36
 
36
37
  if (style.opacity !== undefined) {
@@ -45,16 +46,16 @@ const getTextStyle = (style) => {
45
46
  }
46
47
 
47
48
  if (style.align !== undefined) {
48
- textStyle.align = resolveDynamicValue(style.align, object);
49
+ textStyle.align = resolveDynamicValue(style.align, sprite);
49
50
  }
50
51
 
51
52
  return textStyle;
52
53
  };
53
54
 
54
- const textValue = computed(() => String(resolveDynamicValue(read(value, ''), object) ?? ''));
55
+ const textValue = computed(() => String(resolveDynamicValue(read(value, ''), sprite) ?? ''));
55
56
  const textColor = computed(() => {
56
57
  const currentStyle = read(style, {});
57
- return currentStyle.fill !== undefined ? resolveDynamicValue(currentStyle.fill, object) : undefined;
58
+ return currentStyle.fill !== undefined ? resolveDynamicValue(currentStyle.fill, sprite) : undefined;
58
59
  });
59
60
  const textSize = computed(() => {
60
61
  const currentStyle = read(style, {});
@@ -62,7 +63,7 @@ const textSize = computed(() => {
62
63
  });
63
64
  const textFontFamily = computed(() => {
64
65
  const currentStyle = read(style, {});
65
- return currentStyle.fontFamily !== undefined ? resolveDynamicValue(currentStyle.fontFamily, object) : undefined;
66
+ return currentStyle.fontFamily !== undefined ? resolveDynamicValue(currentStyle.fontFamily, sprite) : undefined;
66
67
  });
67
68
  const textStyle = computed(() => getTextStyle(read(style, {})));
68
69
  </script>
@@ -53,6 +53,7 @@
53
53
  import { inject } from "../../../core/inject";
54
54
  import { RpgClientEngine } from "../../../RpgClientEngine";
55
55
  import { delay } from "@rpgjs/common";
56
+ import { getKeyboardControlBind } from "../../../services/actionInput";
56
57
 
57
58
  const engine = inject(RpgClientEngine);
58
59
  const currentPlayer = engine.scene.currentPlayer
@@ -172,7 +173,7 @@
172
173
  }
173
174
  },
174
175
  action: {
175
- bind: keyboardControls.action,
176
+ bind: getKeyboardControlBind(keyboardControls.action),
176
177
  keyDown() {
177
178
  if (isTyping()) {
178
179
  finishTyping();
@@ -189,7 +190,7 @@
189
190
 
190
191
  const dialogControls = signal({
191
192
  action: {
192
- bind: keyboardControls.action,
193
+ bind: getKeyboardControlBind(keyboardControls.action),
193
194
  keyDown() {
194
195
  if (isTyping()) {
195
196
  finishTyping();
@@ -31,6 +31,7 @@
31
31
  import { inject } from "../../core/inject";
32
32
  import { RpgClientEngine } from "../../RpgClientEngine";
33
33
  import { RpgGui } from "../../Gui/Gui";
34
+ import { getKeyboardControlBind } from "../../services/actionInput";
34
35
 
35
36
  const engine = inject(RpgClientEngine);
36
37
  const guiService = inject(RpgGui);
@@ -146,7 +147,7 @@
146
147
  }
147
148
  },
148
149
  action: {
149
- bind: keyboardControls.action,
150
+ bind: getKeyboardControlBind(keyboardControls.action),
150
151
  keyDown() {
151
152
  triggerSelect(selectedEntry());
152
153
  }