@rpgjs/client 5.0.0-beta.3 → 5.0.0-beta.5

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.ce.js","names":[],"sources":["../../../../src/components/gui/dialogbox/index.ce"],"sourcesContent":["<DOMContainer width=\"100%\" height=\"100%\" controls={dialogControls}>\n <div\n class=\"rpg-ui-dialog-container\"\n data-position={dialogPosition()}\n data-full-width={isFullWidth() ? \"true\" : \"false\"}\n data-has-face={hasFace() ? \"true\" : \"false\"}\n >\n <div class=\"rpg-ui-dialog rpg-anim-fade-in\">\n <div class=\"rpg-ui-dialog-body\">\n <div>\n @if (speakerName()) {\n <div class=\"rpg-ui-dialog-speaker\">{speakerName()}</div>\n }\n <div class=\"rpg-ui-dialog-content\">\n {displayMessage()}\n </div>\n @if (hasChoices()) {\n <Navigation tabindex={selectedItem} controls={controls}>\n <div class=\"rpg-ui-dialog-choices\">\n @for ((choice,index) of choices) {\n <div\n class=\"rpg-ui-dialog-choice\"\n class={{active: selectedItem() === index}}\n tabindex={index}\n data-choice-index={index}\n click={selectChoice(index)}\n >{{ choice.text }}</div>\n }\n </div>\n </Navigation>\n }\n </div>\n @if (hasFace()) {\n <div class=\"rpg-ui-dialog-face\">\n <DOMSprite sheet={faceSheet(face.id, face.expression)} />\n </div>\n }\n </div>\n @if (showIndicator) {\n <div class=\"rpg-ui-dialog-indicator\"></div>\n }\n </div>\n </div>\n</DOMContainer>\n \n<script>\n import { effect, signal, computed, createTabindexNavigator, mount } from \"canvasengine\";\n import { inject } from \"../../../core/inject\";\n import { RpgClientEngine } from \"../../../RpgClientEngine\";\n import { delay } from \"@rpgjs/common\";\n\n const engine = inject(RpgClientEngine);\n const currentPlayer = engine.scene.currentPlayer\n const keyboardControls = engine.globalConfig.keyboardControls;\n\n engine.stopProcessingInput = true;\n\n const selectedItem = signal(0)\n let isDestroyed = false;\n\n const {\n data,\n onFinish,\n onInteraction\n } = defineProps();\n\n const { message, choices, face, speaker, position, typewriterEffect, autoClose } = data();\n const fullWidth = computed(() => data().fullWidth || false);\n\n const resolveProp = (value) => typeof value === \"function\" ? value() : value;\n\n const speakerName = computed(() => {\n const value = resolveProp(speaker);\n return value ? String(value) : \"\";\n });\n\n const dialogPosition = computed(() => resolveProp(position) || \"bottom\");\n const isFullWidth = computed(() => resolveProp(fullWidth) !== false);\n const hasFace = computed(() => !!resolveProp(face));\n\n const displayMessage = signal(\"\");\n const fullMessage = signal(\"\");\n const isTyping = signal(false);\n let typewriterTimer = null;\n let rootElement = null;\n\n mount((element) => {\n rootElement = element;\n });\n\n const startTypewriter = (text) => {\n if (typewriterTimer) clearInterval(typewriterTimer);\n displayMessage.set(\"\");\n if (!text) return;\n let index = 0;\n isTyping.set(true);\n typewriterTimer = setInterval(() => {\n index += 1;\n displayMessage.set(text.slice(0, index));\n if (index >= text.length) {\n clearInterval(typewriterTimer);\n typewriterTimer = null;\n isTyping.set(false);\n }\n }, 20);\n };\n\n const finishTyping = () => {\n if (typewriterTimer) clearInterval(typewriterTimer);\n typewriterTimer = null;\n displayMessage.set(fullMessage());\n isTyping.set(false);\n };\n\n effect(() => {\n const text = resolveProp(message) || \"\";\n fullMessage.set(text);\n const useTypewriter = resolveProp(typewriterEffect) !== false;\n if (!useTypewriter) {\n finishTyping();\n return;\n }\n startTypewriter(text);\n });\n\n\n const hasChoices = computed(() => choices.length > 0);\n const showIndicator = computed(() => !hasChoices() && !isTyping());\n const nav = createTabindexNavigator(selectedItem, { count: () => choices.length }, 'wrap');\n\n function selectChoice(index) {\n return function() {\n selectedItem.set(index);\n onSelect(index);\n }\n }\n\n function _onFinish(value) {\n if (onFinish) onFinish(value);\n }\n\n const onSelect = (index) => {\n _onFinish(index);\n };\n\n const controls = signal({\n up: {\n repeat: true,\n bind: keyboardControls.up,\n throttle: 150,\n keyDown() {\n if (!hasChoices()) return;\n nav.next(-1);\n }\n },\n down: {\n repeat: true,\n bind: keyboardControls.down,\n throttle: 150,\n keyDown() {\n if (!hasChoices()) return;\n nav.next(1);\n }\n },\n action: {\n bind: keyboardControls.action,\n keyDown() {\n if (isTyping()) {\n finishTyping();\n return;\n }\n if (!hasChoices()) return;\n onSelect(selectedItem());\n }\n },\n gamepad: {\n enabled: true\n }\n });\n \n const dialogControls = signal({\n action: {\n bind: keyboardControls.action,\n keyDown() {\n if (isTyping()) {\n finishTyping();\n return;\n }\n if (hasChoices()) return;\n _onFinish();\n }\n },\n })\n\n const faceSheet = (graphicId, animationName) => {\n return {\n definition: engine.getSpriteSheet(graphicId),\n playing: animationName,\n };\n }\n\n mount((element) => {\n return () => {\n isDestroyed = true;\n // Wait destroy is finished before start processing input\n delay(() => {\n engine.stopProcessingInput = false;\n })\n }\n })\n</script>\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.ce.js","names":[],"sources":["../../../../src/components/gui/dialogbox/index.ce"],"sourcesContent":["<DOMContainer width=\"100%\" height=\"100%\" controls={dialogControls}>\n <div\n class=\"rpg-ui-dialog-container\"\n data-position={dialogPosition()}\n data-full-width={isFullWidth() ? \"true\" : \"false\"}\n data-has-face={hasFace() ? \"true\" : \"false\"}\n >\n <div class=\"rpg-ui-dialog rpg-anim-fade-in\">\n <div class=\"rpg-ui-dialog-body\">\n <div>\n @if (speakerName()) {\n <div class=\"rpg-ui-dialog-speaker\">{speakerName()}</div>\n }\n <div class=\"rpg-ui-dialog-content\">\n {displayMessage()}\n </div>\n @if (hasChoices()) {\n <Navigation tabindex={selectedItem} controls={controls}>\n <div class=\"rpg-ui-dialog-choices\">\n @for ((choice,index) of choices) {\n <div\n class=\"rpg-ui-dialog-choice\"\n class={{active: selectedItem() === index}}\n tabindex={index}\n data-choice-index={index}\n click={selectChoice(index)}\n >{{ choice.text }}</div>\n }\n </div>\n </Navigation>\n }\n </div>\n @if (hasFace()) {\n <div class=\"rpg-ui-dialog-face\">\n <DOMSprite\n sheet={faceSheet(dialogFace())}\n width=\"100%\"\n height=\"100%\"\n objectFit=\"contain\"\n />\n </div>\n }\n </div>\n @if (showIndicator) {\n <div class=\"rpg-ui-dialog-indicator\"></div>\n }\n </div>\n </div>\n</DOMContainer>\n \n<script>\n import { effect, signal, computed, createTabindexNavigator, mount } from \"canvasengine\";\n import { inject } from \"../../../core/inject\";\n import { RpgClientEngine } from \"../../../RpgClientEngine\";\n import { delay } from \"@rpgjs/common\";\n\n const engine = inject(RpgClientEngine);\n const currentPlayer = engine.scene.currentPlayer\n const keyboardControls = engine.globalConfig.keyboardControls;\n\n engine.stopProcessingInput = true;\n\n const selectedItem = signal(0)\n let isDestroyed = false;\n\n const {\n data,\n onFinish,\n onInteraction\n } = defineProps();\n\n const { message, choices, face, speaker, position, typewriterEffect, autoClose } = data();\n const fullWidth = computed(() => data().fullWidth || false);\n\n const resolveProp = (value) => typeof value === \"function\" ? value() : value;\n\n const speakerName = computed(() => {\n const value = resolveProp(speaker);\n return value ? String(value) : \"\";\n });\n\n const dialogPosition = computed(() => resolveProp(position) || \"bottom\");\n const isFullWidth = computed(() => resolveProp(fullWidth) !== false);\n const dialogFace = computed(() => resolveProp(face));\n const hasFace = computed(() => {\n const value = dialogFace();\n return !!(value && value.id);\n });\n\n const displayMessage = signal(\"\");\n const fullMessage = signal(\"\");\n const isTyping = signal(false);\n let typewriterTimer = null;\n let rootElement = null;\n\n mount((element) => {\n rootElement = element;\n });\n\n const startTypewriter = (text) => {\n if (typewriterTimer) clearInterval(typewriterTimer);\n displayMessage.set(\"\");\n if (!text) return;\n let index = 0;\n isTyping.set(true);\n typewriterTimer = setInterval(() => {\n index += 1;\n displayMessage.set(text.slice(0, index));\n if (index >= text.length) {\n clearInterval(typewriterTimer);\n typewriterTimer = null;\n isTyping.set(false);\n }\n }, 20);\n };\n\n const finishTyping = () => {\n if (typewriterTimer) clearInterval(typewriterTimer);\n typewriterTimer = null;\n displayMessage.set(fullMessage());\n isTyping.set(false);\n };\n\n effect(() => {\n const text = resolveProp(message) || \"\";\n fullMessage.set(text);\n const useTypewriter = resolveProp(typewriterEffect) !== false;\n if (!useTypewriter) {\n finishTyping();\n return;\n }\n startTypewriter(text);\n });\n\n\n const hasChoices = computed(() => choices.length > 0);\n const showIndicator = computed(() => !hasChoices() && !isTyping());\n const nav = createTabindexNavigator(selectedItem, { count: () => choices.length }, 'wrap');\n\n function selectChoice(index) {\n return function() {\n selectedItem.set(index);\n onSelect(index);\n }\n }\n\n function _onFinish(value) {\n if (onFinish) onFinish(value);\n }\n\n const onSelect = (index) => {\n _onFinish(index);\n };\n\n const controls = signal({\n up: {\n repeat: true,\n bind: keyboardControls.up,\n throttle: 150,\n keyDown() {\n if (!hasChoices()) return;\n nav.next(-1);\n }\n },\n down: {\n repeat: true,\n bind: keyboardControls.down,\n throttle: 150,\n keyDown() {\n if (!hasChoices()) return;\n nav.next(1);\n }\n },\n action: {\n bind: keyboardControls.action,\n keyDown() {\n if (isTyping()) {\n finishTyping();\n return;\n }\n if (!hasChoices()) return;\n onSelect(selectedItem());\n }\n },\n gamepad: {\n enabled: true\n }\n });\n \n const dialogControls = signal({\n action: {\n bind: keyboardControls.action,\n keyDown() {\n if (isTyping()) {\n finishTyping();\n return;\n }\n if (hasChoices()) return;\n _onFinish();\n }\n },\n })\n\n const faceSheet = (faceValue) => {\n return {\n definition: engine.getSpriteSheet(faceValue.id),\n playing: faceValue.expression || \"default\",\n };\n }\n\n mount((element) => {\n return () => {\n isDestroyed = true;\n // Wait destroy is finished before start processing input\n delay(() => {\n engine.stopProcessingInput = false;\n })\n }\n })\n</script>\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpgjs/client",
3
- "version": "5.0.0-beta.3",
3
+ "version": "5.0.0-beta.5",
4
4
  "description": "RPGJS is a framework for creating RPG/MMORPG games",
5
5
  "main": "dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -22,9 +22,9 @@
22
22
  "pixi.js": "^8.9.2"
23
23
  },
24
24
  "dependencies": {
25
- "@rpgjs/common": "5.0.0-beta.3",
26
- "@rpgjs/server": "5.0.0-beta.3",
27
- "@rpgjs/ui-css": "5.0.0-beta.3",
25
+ "@rpgjs/common": "5.0.0-beta.5",
26
+ "@rpgjs/server": "5.0.0-beta.5",
27
+ "@rpgjs/ui-css": "5.0.0-beta.5",
28
28
  "@signe/di": "^2.9.0",
29
29
  "@signe/room": "^2.9.0",
30
30
  "@signe/sync": "^2.9.0",
@@ -1,6 +1,6 @@
1
1
  import { Hooks, ModulesToken, RpgCommonPlayer } from "@rpgjs/common";
2
2
  import { trigger, signal, effect } from "canvasengine";
3
- import { filter, from, map, Subscription, switchMap } from "rxjs";
3
+ import { filter, from, map, of, Subscription, switchMap } from "rxjs";
4
4
  import { inject } from "../core/inject";
5
5
  import { RpgClientEngine } from "../RpgClientEngine";
6
6
  import TextComponent from "../components/dynamics/text.ce";
@@ -11,6 +11,11 @@ const DYNAMIC_COMPONENTS = {
11
11
 
12
12
  type Frame = { x: number; y: number; ts: number };
13
13
 
14
+ type AnimationRestoreOptions = {
15
+ restoreAnimationName?: string;
16
+ restoreGraphics?: any[];
17
+ };
18
+
14
19
  export abstract class RpgClientObject extends RpgCommonPlayer {
15
20
  abstract _type: string;
16
21
  emitParticleTrigger = trigger();
@@ -22,6 +27,10 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
22
27
  graphicsSignals = signal<any[]>([]);
23
28
  _component = {} // temporary component memory
24
29
  flashTrigger = trigger();
30
+ private animationRestoreState?: {
31
+ animationName: string;
32
+ graphics: any[];
33
+ };
25
34
 
26
35
  constructor() {
27
36
  super();
@@ -39,8 +48,10 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
39
48
  this.graphics.observable
40
49
  .pipe(
41
50
  map(({ items }) => items),
42
- filter(graphics => graphics.length > 0),
43
- switchMap(graphics => from(Promise.all(graphics.map(graphic => this.engine.getSpriteSheet(graphic)))))
51
+ switchMap(graphics => {
52
+ if (graphics.length === 0) return of([]);
53
+ return from(Promise.all(graphics.map(graphic => this.engine.getSpriteSheet(graphic))));
54
+ })
44
55
  )
45
56
  .subscribe((sheets) => {
46
57
  this.graphicsSignals.set(sheets);
@@ -101,6 +112,30 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
101
112
  }
102
113
 
103
114
  private animationSubscription?: Subscription;
115
+ private animationResetTimeout?: ReturnType<typeof setTimeout>;
116
+
117
+ private clearAnimationControls() {
118
+ if (this.animationSubscription) {
119
+ this.animationSubscription.unsubscribe();
120
+ this.animationSubscription = undefined;
121
+ }
122
+ if (this.animationResetTimeout) {
123
+ clearTimeout(this.animationResetTimeout);
124
+ this.animationResetTimeout = undefined;
125
+ }
126
+ }
127
+
128
+ private finishTemporaryAnimation() {
129
+ const restoreState = this.animationRestoreState;
130
+ this.clearAnimationControls();
131
+ this.animationCurrentIndex.set(0);
132
+ if (restoreState) {
133
+ this.animationName.set(restoreState.animationName);
134
+ this.graphics.set([...restoreState.graphics]);
135
+ }
136
+ this.animationRestoreState = undefined;
137
+ this.animationIsPlaying.set(false);
138
+ }
104
139
 
105
140
  /**
106
141
  * Trigger a flash animation on this sprite
@@ -199,12 +234,13 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
199
234
  * ```
200
235
  */
201
236
  resetAnimationState() {
237
+ if (this.animationRestoreState) {
238
+ this.finishTemporaryAnimation();
239
+ return;
240
+ }
202
241
  this.animationIsPlaying.set(false);
203
242
  this.animationCurrentIndex.set(0);
204
- if (this.animationSubscription) {
205
- this.animationSubscription.unsubscribe();
206
- this.animationSubscription = undefined;
207
- }
243
+ this.clearAnimationControls();
208
244
  }
209
245
 
210
246
  /**
@@ -226,7 +262,7 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
226
262
  * player.setAnimation('spell');
227
263
  * ```
228
264
  */
229
- setAnimation(animationName: string, nbTimes?: number): void;
265
+ setAnimation(animationName: string, nbTimes?: number, options?: AnimationRestoreOptions): void;
230
266
  /**
231
267
  * Set a custom animation with temporary graphic change
232
268
  *
@@ -244,30 +280,52 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
244
280
  * player.setAnimation('attack', 'hero_attack', 3);
245
281
  * ```
246
282
  */
247
- setAnimation(animationName: string, graphic?: string | string[], nbTimes?: number): void;
248
- setAnimation(animationName: string, graphicOrNbTimes?: string | string[] | number, nbTimes?: number): void {
249
- if (this.animationIsPlaying()) return;
250
- this.animationIsPlaying.set(true);
251
- const previousAnimationName = this.animationName();
252
- const previousGraphics = this.graphics();
253
- this.animationCurrentIndex.set(0);
254
-
283
+ setAnimation(animationName: string, graphic?: string | string[], nbTimes?: number, options?: AnimationRestoreOptions): void;
284
+ setAnimation(
285
+ animationName: string,
286
+ graphicOrNbTimes?: string | string[] | number,
287
+ nbTimesOrOptions?: number | AnimationRestoreOptions,
288
+ options?: AnimationRestoreOptions
289
+ ): void {
255
290
  let graphic: string | string[] | undefined;
256
291
  let finalNbTimes: number = Infinity;
292
+ let restoreOptions: AnimationRestoreOptions | undefined = options;
257
293
 
258
294
  // Handle overloads
259
295
  if (typeof graphicOrNbTimes === 'number') {
260
296
  // setAnimation(animationName, nbTimes)
261
297
  finalNbTimes = graphicOrNbTimes;
298
+ restoreOptions = typeof nbTimesOrOptions === 'object' ? nbTimesOrOptions : options;
262
299
  } else if (graphicOrNbTimes !== undefined) {
263
300
  // setAnimation(animationName, graphic, nbTimes)
264
301
  graphic = graphicOrNbTimes;
265
- finalNbTimes = nbTimes ?? Infinity;
302
+ if (typeof nbTimesOrOptions === 'number') {
303
+ finalNbTimes = nbTimesOrOptions;
304
+ } else {
305
+ finalNbTimes = Infinity;
306
+ restoreOptions = nbTimesOrOptions ?? options;
307
+ }
266
308
  } else {
267
309
  // setAnimation(animationName) - nbTimes remains Infinity
268
310
  finalNbTimes = Infinity;
269
311
  }
270
312
 
313
+ if (this.animationIsPlaying()) {
314
+ this.finishTemporaryAnimation();
315
+ }
316
+
317
+ this.animationIsPlaying.set(true);
318
+ const previousAnimationName =
319
+ restoreOptions?.restoreAnimationName ?? this.animationName();
320
+ const previousGraphics = restoreOptions?.restoreGraphics
321
+ ? [...restoreOptions.restoreGraphics]
322
+ : [...this.graphics()];
323
+ this.animationRestoreState = {
324
+ animationName: previousAnimationName,
325
+ graphics: previousGraphics,
326
+ };
327
+ this.animationCurrentIndex.set(0);
328
+
271
329
  // Temporarily change graphic if provided
272
330
  if (graphic !== undefined) {
273
331
  if (Array.isArray(graphic)) {
@@ -277,27 +335,23 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
277
335
  }
278
336
  }
279
337
 
280
- // Clean up any existing subscription
281
- if (this.animationSubscription) {
282
- this.animationSubscription.unsubscribe();
283
- }
338
+ this.clearAnimationControls();
284
339
 
285
340
  this.animationSubscription =
286
341
  this.animationCurrentIndex.observable.subscribe((index) => {
287
342
  if (index >= finalNbTimes) {
288
- this.animationCurrentIndex.set(0);
289
- this.animationName.set(previousAnimationName);
290
- // Reset graphic to previous value if it was changed
291
- if (graphic !== undefined) {
292
- this.graphics.set(previousGraphics);
293
- }
294
- this.animationIsPlaying.set(false);
295
- if (this.animationSubscription) {
296
- this.animationSubscription.unsubscribe();
297
- this.animationSubscription = undefined;
298
- }
343
+ this.finishTemporaryAnimation();
299
344
  }
300
345
  });
346
+
347
+ if (finalNbTimes !== Infinity) {
348
+ this.animationResetTimeout = setTimeout(() => {
349
+ if (this.animationIsPlaying()) {
350
+ this.finishTemporaryAnimation();
351
+ }
352
+ }, Math.max(1000, finalNbTimes * 1000));
353
+ }
354
+
301
355
  this.animationName.set(animationName);
302
356
  }
303
357
 
@@ -374,15 +374,27 @@ export class RpgClientEngine<T = any> {
374
374
  this.notificationManager.add(data);
375
375
  });
376
376
 
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
- })
377
+ this.webSocket.on("setAnimation", (data) => {
378
+ const {
379
+ animationName,
380
+ nbTimes,
381
+ object,
382
+ graphic,
383
+ restoreAnimationName,
384
+ restoreGraphics,
385
+ } = data;
386
+ const player = object ? this.sceneMap.getObjectById(object) : undefined;
387
+ if (!player) return;
388
+ const restoreOptions = {
389
+ restoreAnimationName,
390
+ restoreGraphics,
391
+ };
392
+ if (graphic !== undefined) {
393
+ player.setAnimation(animationName, graphic, nbTimes, restoreOptions);
394
+ } else {
395
+ player.setAnimation(animationName, nbTimes, restoreOptions);
396
+ }
397
+ })
386
398
 
387
399
  this.webSocket.on("playSound", (data) => {
388
400
  const { soundId, volume, loop } = data;
@@ -1174,6 +1186,18 @@ export class RpgClientEngine<T = any> {
1174
1186
  }
1175
1187
 
1176
1188
  async processInput({ input }: { input: Direction }) {
1189
+ if (this.stopProcessingInput) return;
1190
+
1191
+ const currentPlayer = this.sceneMap.getCurrentPlayer() as any;
1192
+ const canMove =
1193
+ !currentPlayer ||
1194
+ typeof currentPlayer.canMove !== "function" ||
1195
+ currentPlayer.canMove();
1196
+ if (!canMove) {
1197
+ this.interruptCurrentPlayerMovement(currentPlayer);
1198
+ return;
1199
+ }
1200
+
1177
1201
  const timestamp = Date.now();
1178
1202
  let frame: number;
1179
1203
  let tick: number;
@@ -1188,7 +1212,6 @@ export class RpgClientEngine<T = any> {
1188
1212
  this.inputFrameCounter = frame;
1189
1213
  this.hooks.callHooks("client-engine-onInput", this, { input, playerId: this.playerId }).subscribe();
1190
1214
 
1191
- const currentPlayer = this.sceneMap.getCurrentPlayer();
1192
1215
  const bodyReady = this.ensureCurrentPlayerBody();
1193
1216
  if (currentPlayer && bodyReady) {
1194
1217
  currentPlayer.changeDirection(input);
@@ -1207,6 +1230,13 @@ export class RpgClientEngine<T = any> {
1207
1230
 
1208
1231
  processAction({ action }: { action: number }) {
1209
1232
  if (this.stopProcessingInput) return;
1233
+ const currentPlayer = this.sceneMap.getCurrentPlayer() as any;
1234
+ const canMove =
1235
+ !currentPlayer ||
1236
+ typeof currentPlayer.canMove !== "function" ||
1237
+ currentPlayer.canMove();
1238
+ if (!canMove) return;
1239
+
1210
1240
  this.hooks.callHooks("client-engine-onInput", this, { input: 'action', playerId: this.playerId }).subscribe();
1211
1241
  this.webSocket.emit('action', { action })
1212
1242
  }
@@ -1337,6 +1367,15 @@ export class RpgClientEngine<T = any> {
1337
1367
  if (!this.predictionEnabled || !this.prediction) {
1338
1368
  return;
1339
1369
  }
1370
+ const player = this.sceneMap?.getCurrentPlayer?.() as any;
1371
+ if (
1372
+ player &&
1373
+ typeof player.canMove === "function" &&
1374
+ !player.canMove()
1375
+ ) {
1376
+ this.interruptCurrentPlayerMovement(player);
1377
+ return;
1378
+ }
1340
1379
  const pendingInputs = this.prediction.getPendingInputs();
1341
1380
  if (pendingInputs.length === 0) {
1342
1381
  return;
@@ -1475,6 +1514,34 @@ export class RpgClientEngine<T = any> {
1475
1514
  this.lastMovePathSentFrame = 0;
1476
1515
  }
1477
1516
 
1517
+ /**
1518
+ * Stop local movement immediately and discard pending predicted movement.
1519
+ *
1520
+ * Use this before a blocking action such as an A-RPG attack, dialog, dash
1521
+ * startup, or any client-side state where already buffered movement inputs
1522
+ * must not be replayed after server reconciliation.
1523
+ *
1524
+ * @param player - Player object to stop. Defaults to the current player.
1525
+ * @returns `true` when a player was found and interrupted.
1526
+ *
1527
+ * @example
1528
+ * ```ts
1529
+ * engine.interruptCurrentPlayerMovement();
1530
+ * ```
1531
+ */
1532
+ interruptCurrentPlayerMovement(player: any = this.sceneMap?.getCurrentPlayer?.()): boolean {
1533
+ if (!player) {
1534
+ return false;
1535
+ }
1536
+ (this.sceneMap as any)?.stopMovement?.(player);
1537
+ this.prediction?.clearPendingInputs();
1538
+ this.pendingPredictionFrames = [];
1539
+ this.lastInputTime = 0;
1540
+ this.lastMovePathSentAt = Date.now();
1541
+ this.lastMovePathSentFrame = this.inputFrameCounter;
1542
+ return true;
1543
+ }
1544
+
1478
1545
  /**
1479
1546
  * Trigger a flash animation on a sprite
1480
1547
  *
@@ -1560,7 +1627,7 @@ export class RpgClientEngine<T = any> {
1560
1627
  if (typeof ack.x !== "number" || typeof ack.y !== "number") {
1561
1628
  return;
1562
1629
  }
1563
- const player = this.getCurrentPlayer();
1630
+ const player = this.getCurrentPlayer() as any;
1564
1631
  const myId = this.playerIdSignal();
1565
1632
  if (!player || !myId) {
1566
1633
  return;
@@ -1583,10 +1650,14 @@ export class RpgClientEngine<T = any> {
1583
1650
  authoritativeState: PredictionState<Direction>,
1584
1651
  pendingInputs: PredictionHistoryEntry<Direction>[],
1585
1652
  ): void {
1586
- const player = this.getCurrentPlayer();
1653
+ const player = this.getCurrentPlayer() as any;
1587
1654
  if (!player) {
1588
1655
  return;
1589
1656
  }
1657
+ if (typeof player.canMove === "function" && !player.canMove()) {
1658
+ this.interruptCurrentPlayerMovement(player);
1659
+ return;
1660
+ }
1590
1661
 
1591
1662
  (this.sceneMap as any).stopMovement(player);
1592
1663
  this.applyAuthoritativeState(authoritativeState);
@@ -9,6 +9,7 @@
9
9
  @for (graphicObj of graphicsSignals) {
10
10
  <Sprite
11
11
  sheet={sheet(graphicObj)}
12
+ scale={graphicScale(graphicObj)}
12
13
  direction
13
14
  tint
14
15
  hitbox
@@ -377,6 +378,18 @@
377
378
  };
378
379
  }
379
380
 
381
+ const graphicScale = (graphicObject) => {
382
+ const scale = graphicObject?.scale;
383
+ if (Array.isArray(scale)) return scale;
384
+ if (typeof scale === 'number') return [scale, scale];
385
+ if (scale && typeof scale === 'object') {
386
+ const x = typeof scale.x === 'number' ? scale.x : 1;
387
+ const y = typeof scale.y === 'number' ? scale.y : x;
388
+ return [x, y];
389
+ }
390
+ return undefined;
391
+ }
392
+
380
393
  // Combine animation change detection with movement state from smoothX/smoothY
381
394
  const movementAnimations = ['walk', 'stand'];
382
395
  const epsilon = 0; // movement threshold to consider the easing still running
@@ -467,4 +480,4 @@
467
480
  tick(() => {
468
481
  hooks.callHooks("client-sprite-onUpdate").subscribe()
469
482
  })
470
- </script>
483
+ </script>
@@ -29,10 +29,15 @@
29
29
  </div>
30
30
  </Navigation>
31
31
  }
32
- </div>
32
+ </div>
33
33
  @if (hasFace()) {
34
34
  <div class="rpg-ui-dialog-face">
35
- <DOMSprite sheet={faceSheet(face.id, face.expression)} />
35
+ <DOMSprite
36
+ sheet={faceSheet(dialogFace())}
37
+ width="100%"
38
+ height="100%"
39
+ objectFit="contain"
40
+ />
36
41
  </div>
37
42
  }
38
43
  </div>
@@ -76,7 +81,11 @@
76
81
 
77
82
  const dialogPosition = computed(() => resolveProp(position) || "bottom");
78
83
  const isFullWidth = computed(() => resolveProp(fullWidth) !== false);
79
- const hasFace = computed(() => !!resolveProp(face));
84
+ const dialogFace = computed(() => resolveProp(face));
85
+ const hasFace = computed(() => {
86
+ const value = dialogFace();
87
+ return !!(value && value.id);
88
+ });
80
89
 
81
90
  const displayMessage = signal("");
82
91
  const fullMessage = signal("");
@@ -192,10 +201,10 @@
192
201
  },
193
202
  })
194
203
 
195
- const faceSheet = (graphicId, animationName) => {
204
+ const faceSheet = (faceValue) => {
196
205
  return {
197
- definition: engine.getSpriteSheet(graphicId),
198
- playing: animationName,
206
+ definition: engine.getSpriteSheet(faceValue.id),
207
+ playing: faceValue.expression || "default",
199
208
  };
200
209
  }
201
210