@rpgjs/client 5.0.0-beta.1 → 5.0.0-beta.11
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.
- package/CHANGELOG.md +49 -0
- package/LICENSE +19 -0
- package/dist/Game/AnimationManager.d.ts +1 -1
- package/dist/Game/AnimationManager.js +18 -9
- package/dist/Game/AnimationManager.js.map +1 -1
- package/dist/Game/AnimationManager.spec.d.ts +1 -0
- package/dist/Game/Event.js.map +1 -1
- package/dist/Game/Map.d.ts +9 -1
- package/dist/Game/Map.js +63 -5
- package/dist/Game/Map.js.map +1 -1
- package/dist/Game/Object.d.ts +47 -15
- package/dist/Game/Object.js +82 -38
- package/dist/Game/Object.js.map +1 -1
- package/dist/Game/Player.js.map +1 -1
- package/dist/Game/ProjectileManager.d.ts +89 -0
- package/dist/Game/ProjectileManager.js +179 -0
- package/dist/Game/ProjectileManager.js.map +1 -0
- package/dist/Game/ProjectileManager.spec.d.ts +1 -0
- package/dist/Gui/Gui.d.ts +17 -4
- package/dist/Gui/Gui.js +78 -48
- package/dist/Gui/Gui.js.map +1 -1
- package/dist/Gui/Gui.spec.d.ts +1 -0
- package/dist/Gui/NotificationManager.js.map +1 -1
- package/dist/Resource.js +1 -1
- package/dist/Resource.js.map +1 -1
- package/dist/RpgClient.d.ts +110 -15
- package/dist/RpgClientEngine.d.ts +86 -10
- package/dist/RpgClientEngine.js +306 -49
- package/dist/RpgClientEngine.js.map +1 -1
- package/dist/Sound.js.map +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.122.0 → _@oxc-project_runtime@0.130.0}/helpers/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.122.0 → _@oxc-project_runtime@0.130.0}/helpers/decorateMetadata.js +1 -1
- package/dist/components/animations/animation.ce.js +4 -5
- package/dist/components/animations/animation.ce.js.map +1 -1
- package/dist/components/animations/hit.ce.js +19 -25
- package/dist/components/animations/hit.ce.js.map +1 -1
- package/dist/components/animations/index.js +4 -4
- package/dist/components/animations/index.js.map +1 -1
- package/dist/components/character.ce.js +422 -240
- package/dist/components/character.ce.js.map +1 -1
- package/dist/components/dynamics/bar.ce.js +97 -0
- package/dist/components/dynamics/bar.ce.js.map +1 -0
- package/dist/components/dynamics/image.ce.js +24 -0
- package/dist/components/dynamics/image.ce.js.map +1 -0
- package/dist/components/dynamics/parse-value.d.ts +3 -0
- package/dist/components/dynamics/parse-value.js +54 -35
- package/dist/components/dynamics/parse-value.js.map +1 -1
- package/dist/components/dynamics/parse-value.spec.d.ts +1 -0
- package/dist/components/dynamics/shape-utils.d.ts +16 -0
- package/dist/components/dynamics/shape-utils.js +73 -0
- package/dist/components/dynamics/shape-utils.js.map +1 -0
- package/dist/components/dynamics/shape-utils.spec.d.ts +1 -0
- package/dist/components/dynamics/shape.ce.js +84 -0
- package/dist/components/dynamics/shape.ce.js.map +1 -0
- package/dist/components/dynamics/text.ce.js +34 -56
- package/dist/components/dynamics/text.ce.js.map +1 -1
- package/dist/components/gui/box.ce.js +6 -8
- package/dist/components/gui/box.ce.js.map +1 -1
- package/dist/components/gui/dialogbox/index.ce.js +56 -62
- package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
- package/dist/components/gui/gameover.ce.js +42 -65
- package/dist/components/gui/gameover.ce.js.map +1 -1
- package/dist/components/gui/hud/hud.ce.js +21 -30
- package/dist/components/gui/hud/hud.ce.js.map +1 -1
- package/dist/components/gui/menu/equip-menu.ce.js +112 -165
- package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/exit-menu.ce.js +8 -6
- package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/items-menu.ce.js +52 -69
- package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/main-menu.ce.js +75 -92
- package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/options-menu.ce.js +5 -4
- package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/skills-menu.ce.js +12 -17
- package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
- package/dist/components/gui/mobile/index.js +2 -2
- package/dist/components/gui/mobile/index.js.map +1 -1
- package/dist/components/gui/mobile/mobile.ce.js +5 -4
- package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
- package/dist/components/gui/notification/notification.ce.js +22 -24
- package/dist/components/gui/notification/notification.ce.js.map +1 -1
- package/dist/components/gui/save-load.ce.js +72 -249
- package/dist/components/gui/save-load.ce.js.map +1 -1
- package/dist/components/gui/shop/shop.ce.js +90 -127
- package/dist/components/gui/shop/shop.ce.js.map +1 -1
- package/dist/components/gui/title-screen.ce.js +45 -70
- package/dist/components/gui/title-screen.ce.js.map +1 -1
- package/dist/components/index.d.ts +2 -1
- package/dist/components/index.js +1 -0
- package/dist/components/player-components-utils.d.ts +67 -0
- package/dist/components/player-components-utils.js +162 -0
- package/dist/components/player-components-utils.js.map +1 -0
- package/dist/components/player-components-utils.spec.d.ts +1 -0
- package/dist/components/player-components.ce.js +189 -0
- package/dist/components/player-components.ce.js.map +1 -0
- package/dist/components/prebuilt/hp-bar.ce.js +42 -44
- package/dist/components/prebuilt/hp-bar.ce.js.map +1 -1
- package/dist/components/prebuilt/light-halo.ce.js +36 -59
- package/dist/components/prebuilt/light-halo.ce.js.map +1 -1
- package/dist/components/scenes/canvas.ce.js +165 -21
- package/dist/components/scenes/canvas.ce.js.map +1 -1
- package/dist/components/scenes/draw-map.ce.js +25 -32
- package/dist/components/scenes/draw-map.ce.js.map +1 -1
- package/dist/components/scenes/event-layer.ce.js +9 -8
- package/dist/components/scenes/event-layer.ce.js.map +1 -1
- package/dist/core/inject.js +1 -1
- package/dist/core/inject.js.map +1 -1
- package/dist/core/setup.js +1 -1
- package/dist/core/setup.js.map +1 -1
- package/dist/decorators/spritesheet.d.ts +1 -0
- package/dist/decorators/spritesheet.js +11 -0
- package/dist/decorators/spritesheet.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +26 -21
- package/dist/module.js +15 -1
- package/dist/module.js.map +1 -1
- package/dist/node_modules/.pnpm/{@signe_di@2.9.0 → @signe_di@3.0.1}/node_modules/@signe/di/dist/index.js +7 -117
- package/dist/node_modules/.pnpm/@signe_di@3.0.1/node_modules/@signe/di/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@signe_reactive@3.0.1/node_modules/@signe/reactive/dist/index.js +239 -0
- package/dist/node_modules/.pnpm/@signe_reactive@3.0.1/node_modules/@signe/reactive/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/chunk-EUXUH3YW.js +13 -0
- package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/chunk-EUXUH3YW.js.map +1 -0
- package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/index.js +696 -0
- package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/client/index.js +44 -0
- package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/client/index.js.map +1 -0
- package/dist/node_modules/.pnpm/{@signe_sync@2.9.0 → @signe_sync@3.0.1}/node_modules/@signe/sync/dist/index.js +57 -141
- package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-HAC622V3.js.map +1 -1
- package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-S74YV6PU.js.map +1 -1
- package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js +27 -27
- package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js.map +1 -1
- package/dist/presets/animation.js.map +1 -1
- package/dist/presets/faceset.js.map +1 -1
- package/dist/presets/icon.js.map +1 -1
- package/dist/presets/index.js.map +1 -1
- package/dist/presets/lpc.js.map +1 -1
- package/dist/presets/rmspritesheet.js.map +1 -1
- package/dist/services/AbstractSocket.js.map +1 -1
- package/dist/services/actionInput.d.ts +12 -0
- package/dist/services/actionInput.js +27 -0
- package/dist/services/actionInput.js.map +1 -0
- package/dist/services/actionInput.spec.d.ts +1 -0
- package/dist/services/keyboardControls.js.map +1 -1
- package/dist/services/loadMap.d.ts +6 -0
- package/dist/services/loadMap.js +1 -1
- package/dist/services/loadMap.js.map +1 -1
- package/dist/services/mmorpg-connection.d.ts +5 -0
- package/dist/services/mmorpg-connection.js +50 -0
- package/dist/services/mmorpg-connection.js.map +1 -0
- package/dist/services/mmorpg-connection.spec.d.ts +1 -0
- package/dist/services/mmorpg.d.ts +10 -4
- package/dist/services/mmorpg.js +56 -33
- package/dist/services/mmorpg.js.map +1 -1
- package/dist/services/pointerContext.d.ts +11 -0
- package/dist/services/pointerContext.js +48 -0
- package/dist/services/pointerContext.js.map +1 -0
- package/dist/services/pointerContext.spec.d.ts +1 -0
- package/dist/services/save.js.map +1 -1
- package/dist/services/save.spec.d.ts +1 -0
- package/dist/services/standalone-message.d.ts +1 -0
- package/dist/services/standalone-message.js +9 -0
- package/dist/services/standalone-message.js.map +1 -0
- package/dist/services/standalone.js +4 -3
- package/dist/services/standalone.js.map +1 -1
- package/dist/services/standalone.spec.d.ts +1 -0
- package/dist/utils/getEntityProp.js +4 -3
- package/dist/utils/getEntityProp.js.map +1 -1
- package/dist/utils/getEntityProp.spec.d.ts +1 -0
- package/dist/utils/readPropValue.d.ts +2 -0
- package/dist/utils/readPropValue.js +13 -0
- package/dist/utils/readPropValue.js.map +1 -0
- package/package.json +13 -14
- package/src/Game/AnimationManager.spec.ts +30 -0
- package/src/Game/AnimationManager.ts +22 -10
- package/src/Game/Map.ts +91 -2
- package/src/Game/Object.ts +148 -69
- package/src/Game/ProjectileManager.spec.ts +338 -0
- package/src/Game/ProjectileManager.ts +324 -0
- package/src/Gui/Gui.spec.ts +273 -0
- package/src/Gui/Gui.ts +105 -50
- package/src/Resource.ts +1 -2
- package/src/RpgClient.ts +125 -17
- package/src/RpgClientEngine.ts +457 -87
- package/src/components/character.ce +441 -32
- package/src/components/dynamics/bar.ce +88 -0
- package/src/components/dynamics/image.ce +21 -0
- package/src/components/dynamics/parse-value.spec.ts +83 -0
- package/src/components/dynamics/parse-value.ts +111 -37
- package/src/components/dynamics/shape-utils.spec.ts +46 -0
- package/src/components/dynamics/shape-utils.ts +61 -0
- package/src/components/dynamics/shape.ce +90 -0
- package/src/components/dynamics/text.ce +35 -149
- package/src/components/gui/dialogbox/index.ce +18 -8
- package/src/components/gui/gameover.ce +2 -1
- package/src/components/gui/menu/equip-menu.ce +2 -1
- package/src/components/gui/menu/exit-menu.ce +2 -1
- package/src/components/gui/menu/items-menu.ce +3 -2
- package/src/components/gui/menu/main-menu.ce +2 -1
- package/src/components/gui/save-load.ce +2 -1
- package/src/components/gui/shop/shop.ce +3 -2
- package/src/components/gui/title-screen.ce +2 -1
- package/src/components/index.ts +2 -1
- package/src/components/player-components-utils.spec.ts +109 -0
- package/src/components/player-components-utils.ts +205 -0
- package/src/components/player-components.ce +222 -0
- package/src/components/prebuilt/hp-bar.ce +4 -3
- package/src/components/prebuilt/light-halo.ce +2 -2
- package/src/components/scenes/canvas.ce +175 -8
- package/src/components/scenes/draw-map.ce +18 -17
- package/src/components/scenes/event-layer.ce +1 -2
- package/src/core/setup.ts +2 -2
- package/src/decorators/spritesheet.ts +8 -0
- package/src/index.ts +4 -0
- package/src/module.ts +18 -1
- package/src/services/actionInput.spec.ts +101 -0
- package/src/services/actionInput.ts +53 -0
- package/src/services/loadMap.ts +2 -0
- package/src/services/mmorpg-connection.spec.ts +99 -0
- package/src/services/mmorpg-connection.ts +69 -0
- package/src/services/mmorpg.ts +68 -36
- package/src/services/pointerContext.spec.ts +36 -0
- package/src/services/pointerContext.ts +84 -0
- package/src/services/save.spec.ts +127 -0
- package/src/services/standalone-message.ts +7 -0
- package/src/services/standalone.spec.ts +34 -0
- package/src/services/standalone.ts +3 -2
- package/src/utils/getEntityProp.spec.ts +96 -0
- package/src/utils/getEntityProp.ts +4 -3
- package/src/utils/readPropValue.ts +16 -0
- package/dist/node_modules/.pnpm/@signe_di@2.9.0/node_modules/@signe/di/dist/index.js.map +0 -1
- package/dist/node_modules/.pnpm/@signe_reactive@2.8.3/node_modules/@signe/reactive/dist/index.js +0 -457
- package/dist/node_modules/.pnpm/@signe_reactive@2.8.3/node_modules/@signe/reactive/dist/index.js.map +0 -1
- package/dist/node_modules/.pnpm/@signe_reactive@2.9.0/node_modules/@signe/reactive/dist/index.js +0 -463
- package/dist/node_modules/.pnpm/@signe_reactive@2.9.0/node_modules/@signe/reactive/dist/index.js.map +0 -1
- package/dist/node_modules/.pnpm/@signe_room@2.9.0/node_modules/@signe/room/dist/index.js +0 -2191
- package/dist/node_modules/.pnpm/@signe_room@2.9.0/node_modules/@signe/room/dist/index.js.map +0 -1
- package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/chunk-7QVYU63E.js +0 -10
- package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/chunk-7QVYU63E.js.map +0 -1
- package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/client/index.js +0 -91
- package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/client/index.js.map +0 -1
- package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/index.js.map +0 -1
- package/dist/node_modules/.pnpm/dset@3.1.4/node_modules/dset/dist/index.js +0 -14
- package/dist/node_modules/.pnpm/dset@3.1.4/node_modules/dset/dist/index.js.map +0 -1
package/src/Game/Object.ts
CHANGED
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
import { Hooks, ModulesToken, RpgCommonPlayer } from "@rpgjs/common";
|
|
2
|
-
import { trigger, signal,
|
|
3
|
-
import {
|
|
2
|
+
import { trigger, signal, type Trigger } from "canvasengine";
|
|
3
|
+
import { from, map, of, Subscription, switchMap } from "rxjs";
|
|
4
4
|
import { inject } from "../core/inject";
|
|
5
5
|
import { RpgClientEngine } from "../RpgClientEngine";
|
|
6
|
-
|
|
6
|
+
type Frame = { x: number; y: number; ts: number };
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
type AnimationRestoreOptions = {
|
|
9
|
+
restoreAnimationName?: string;
|
|
10
|
+
restoreGraphics?: any[];
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
};
|
|
11
13
|
|
|
12
|
-
type
|
|
14
|
+
type FlashType = 'alpha' | 'tint' | 'both';
|
|
15
|
+
|
|
16
|
+
type FlashOptions = {
|
|
17
|
+
type?: FlashType;
|
|
18
|
+
duration?: number;
|
|
19
|
+
cycles?: number;
|
|
20
|
+
alpha?: number;
|
|
21
|
+
tint?: number | string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type FlashTriggerOptions = Omit<FlashOptions, "tint"> & {
|
|
25
|
+
tint: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type ConfigurableTrigger<T> = Omit<Trigger<T>, "start"> & {
|
|
29
|
+
start(config?: T): Promise<void>;
|
|
30
|
+
};
|
|
13
31
|
|
|
14
32
|
export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
15
33
|
abstract _type: string;
|
|
@@ -20,8 +38,11 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
20
38
|
_param = signal({});
|
|
21
39
|
frames: Frame[] = [];
|
|
22
40
|
graphicsSignals = signal<any[]>([]);
|
|
23
|
-
|
|
24
|
-
|
|
41
|
+
flashTrigger: ConfigurableTrigger<FlashTriggerOptions> = trigger<FlashTriggerOptions>();
|
|
42
|
+
private animationRestoreState?: {
|
|
43
|
+
animationName: string;
|
|
44
|
+
graphics: any[];
|
|
45
|
+
};
|
|
25
46
|
|
|
26
47
|
constructor() {
|
|
27
48
|
super();
|
|
@@ -39,31 +60,15 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
39
60
|
this.graphics.observable
|
|
40
61
|
.pipe(
|
|
41
62
|
map(({ items }) => items),
|
|
42
|
-
|
|
43
|
-
|
|
63
|
+
switchMap(graphics => {
|
|
64
|
+
if (graphics.length === 0) return of([]);
|
|
65
|
+
return from(Promise.all(graphics.map(graphic => this.engine.getSpriteSheet(graphic))));
|
|
66
|
+
})
|
|
44
67
|
)
|
|
45
68
|
.subscribe((sheets) => {
|
|
46
69
|
this.graphicsSignals.set(sheets);
|
|
47
70
|
});
|
|
48
71
|
|
|
49
|
-
this.componentsTop.observable
|
|
50
|
-
.pipe(
|
|
51
|
-
filter(value => value !== null && value !== undefined),
|
|
52
|
-
map((value) => typeof value === 'string' ? JSON.parse(value) : value),
|
|
53
|
-
)
|
|
54
|
-
.subscribe(({components}) => {
|
|
55
|
-
for (const component of components) {
|
|
56
|
-
for (const [key, value] of Object.entries(component)) {
|
|
57
|
-
this._component = value as any; // temporary component memory
|
|
58
|
-
console.log(value)
|
|
59
|
-
const type = (value as any).type as keyof typeof DYNAMIC_COMPONENTS;
|
|
60
|
-
if (DYNAMIC_COMPONENTS[type]) {
|
|
61
|
-
this.engine.addSpriteComponentInFront(DYNAMIC_COMPONENTS[type]);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
72
|
this.engine.tick
|
|
68
73
|
.pipe
|
|
69
74
|
//throttleTime(10)
|
|
@@ -101,6 +106,38 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
101
106
|
}
|
|
102
107
|
|
|
103
108
|
private animationSubscription?: Subscription;
|
|
109
|
+
private animationResetTimeout?: ReturnType<typeof setTimeout>;
|
|
110
|
+
private animationWaitResolve?: () => void;
|
|
111
|
+
|
|
112
|
+
private clearAnimationControls() {
|
|
113
|
+
if (this.animationSubscription) {
|
|
114
|
+
this.animationSubscription.unsubscribe();
|
|
115
|
+
this.animationSubscription = undefined;
|
|
116
|
+
}
|
|
117
|
+
if (this.animationResetTimeout) {
|
|
118
|
+
clearTimeout(this.animationResetTimeout);
|
|
119
|
+
this.animationResetTimeout = undefined;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private resolveAnimationWait() {
|
|
124
|
+
const resolve = this.animationWaitResolve;
|
|
125
|
+
this.animationWaitResolve = undefined;
|
|
126
|
+
resolve?.();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private finishTemporaryAnimation() {
|
|
130
|
+
const restoreState = this.animationRestoreState;
|
|
131
|
+
this.clearAnimationControls();
|
|
132
|
+
this.animationCurrentIndex.set(0);
|
|
133
|
+
if (restoreState) {
|
|
134
|
+
this.animationName.set(restoreState.animationName);
|
|
135
|
+
this.graphics.set([...restoreState.graphics]);
|
|
136
|
+
}
|
|
137
|
+
this.animationRestoreState = undefined;
|
|
138
|
+
this.animationIsPlaying.set(false);
|
|
139
|
+
this.resolveAnimationWait();
|
|
140
|
+
}
|
|
104
141
|
|
|
105
142
|
/**
|
|
106
143
|
* Trigger a flash animation on this sprite
|
|
@@ -148,13 +185,7 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
148
185
|
* });
|
|
149
186
|
* ```
|
|
150
187
|
*/
|
|
151
|
-
flash(options?: {
|
|
152
|
-
type?: 'alpha' | 'tint' | 'both';
|
|
153
|
-
duration?: number;
|
|
154
|
-
cycles?: number;
|
|
155
|
-
alpha?: number;
|
|
156
|
-
tint?: number | string;
|
|
157
|
-
}): void {
|
|
188
|
+
flash(options?: FlashOptions): void {
|
|
158
189
|
const flashOptions = {
|
|
159
190
|
type: options?.type || 'alpha',
|
|
160
191
|
duration: options?.duration ?? 300,
|
|
@@ -199,12 +230,14 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
199
230
|
* ```
|
|
200
231
|
*/
|
|
201
232
|
resetAnimationState() {
|
|
233
|
+
if (this.animationRestoreState) {
|
|
234
|
+
this.finishTemporaryAnimation();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
202
237
|
this.animationIsPlaying.set(false);
|
|
203
238
|
this.animationCurrentIndex.set(0);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
this.animationSubscription = undefined;
|
|
207
|
-
}
|
|
239
|
+
this.clearAnimationControls();
|
|
240
|
+
this.resolveAnimationWait();
|
|
208
241
|
}
|
|
209
242
|
|
|
210
243
|
/**
|
|
@@ -216,17 +249,19 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
216
249
|
*
|
|
217
250
|
* @param animationName - Name of the animation to play
|
|
218
251
|
* @param nbTimes - Number of times to repeat the animation (default: Infinity for continuous)
|
|
252
|
+
* @param options - Restore and timeout options
|
|
253
|
+
* @returns A promise resolved when a finite animation finishes, is interrupted, or times out
|
|
219
254
|
*
|
|
220
255
|
* @example
|
|
221
256
|
* ```ts
|
|
222
257
|
* // Play attack animation 3 times
|
|
223
|
-
* player.setAnimation('attack', 3);
|
|
258
|
+
* await player.setAnimation('attack', 3);
|
|
224
259
|
*
|
|
225
260
|
* // Play continuous spell animation
|
|
226
261
|
* player.setAnimation('spell');
|
|
227
262
|
* ```
|
|
228
263
|
*/
|
|
229
|
-
setAnimation(animationName: string, nbTimes?: number): void
|
|
264
|
+
setAnimation(animationName: string, nbTimes?: number, options?: AnimationRestoreOptions): Promise<void>;
|
|
230
265
|
/**
|
|
231
266
|
* Set a custom animation with temporary graphic change
|
|
232
267
|
*
|
|
@@ -237,37 +272,68 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
237
272
|
* @param animationName - Name of the animation to play
|
|
238
273
|
* @param graphic - The graphic(s) to temporarily use during the animation
|
|
239
274
|
* @param nbTimes - Number of times to repeat the animation (default: Infinity for continuous)
|
|
275
|
+
* @param options - Restore and timeout options
|
|
276
|
+
* @returns A promise resolved when a finite animation finishes, is interrupted, or times out
|
|
240
277
|
*
|
|
241
278
|
* @example
|
|
242
279
|
* ```ts
|
|
243
280
|
* // Play attack animation with temporary graphic change
|
|
244
|
-
* player.setAnimation('attack', 'hero_attack', 3);
|
|
281
|
+
* await player.setAnimation('attack', 'hero_attack', 3);
|
|
245
282
|
* ```
|
|
246
283
|
*/
|
|
247
|
-
setAnimation(animationName: string, graphic?: string | string[], nbTimes?: number): void
|
|
248
|
-
setAnimation(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
284
|
+
setAnimation(animationName: string, graphic?: string | string[], nbTimes?: number, options?: AnimationRestoreOptions): Promise<void>;
|
|
285
|
+
setAnimation(
|
|
286
|
+
animationName: string,
|
|
287
|
+
graphicOrNbTimes?: string | string[] | number,
|
|
288
|
+
nbTimesOrOptions?: number | AnimationRestoreOptions,
|
|
289
|
+
options?: AnimationRestoreOptions
|
|
290
|
+
): Promise<void> {
|
|
255
291
|
let graphic: string | string[] | undefined;
|
|
256
292
|
let finalNbTimes: number = Infinity;
|
|
293
|
+
let restoreOptions: AnimationRestoreOptions | undefined = options;
|
|
257
294
|
|
|
258
295
|
// Handle overloads
|
|
259
296
|
if (typeof graphicOrNbTimes === 'number') {
|
|
260
297
|
// setAnimation(animationName, nbTimes)
|
|
261
298
|
finalNbTimes = graphicOrNbTimes;
|
|
299
|
+
restoreOptions = typeof nbTimesOrOptions === 'object' ? nbTimesOrOptions : options;
|
|
262
300
|
} else if (graphicOrNbTimes !== undefined) {
|
|
263
301
|
// setAnimation(animationName, graphic, nbTimes)
|
|
264
302
|
graphic = graphicOrNbTimes;
|
|
265
|
-
|
|
303
|
+
if (typeof nbTimesOrOptions === 'number') {
|
|
304
|
+
finalNbTimes = nbTimesOrOptions;
|
|
305
|
+
} else {
|
|
306
|
+
finalNbTimes = Infinity;
|
|
307
|
+
restoreOptions = nbTimesOrOptions ?? options;
|
|
308
|
+
}
|
|
266
309
|
} else {
|
|
267
310
|
// setAnimation(animationName) - nbTimes remains Infinity
|
|
268
311
|
finalNbTimes = Infinity;
|
|
269
312
|
}
|
|
270
313
|
|
|
314
|
+
if (this.animationIsPlaying()) {
|
|
315
|
+
this.finishTemporaryAnimation();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const waitPromise =
|
|
319
|
+
finalNbTimes === Infinity
|
|
320
|
+
? Promise.resolve()
|
|
321
|
+
: new Promise<void>((resolve) => {
|
|
322
|
+
this.animationWaitResolve = resolve;
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
this.animationIsPlaying.set(true);
|
|
326
|
+
const previousAnimationName =
|
|
327
|
+
restoreOptions?.restoreAnimationName ?? this.animationName();
|
|
328
|
+
const previousGraphics = restoreOptions?.restoreGraphics
|
|
329
|
+
? [...restoreOptions.restoreGraphics]
|
|
330
|
+
: [...this.graphics()];
|
|
331
|
+
this.animationRestoreState = {
|
|
332
|
+
animationName: previousAnimationName,
|
|
333
|
+
graphics: previousGraphics,
|
|
334
|
+
};
|
|
335
|
+
this.animationCurrentIndex.set(0);
|
|
336
|
+
|
|
271
337
|
// Temporarily change graphic if provided
|
|
272
338
|
if (graphic !== undefined) {
|
|
273
339
|
if (Array.isArray(graphic)) {
|
|
@@ -277,28 +343,26 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
277
343
|
}
|
|
278
344
|
}
|
|
279
345
|
|
|
280
|
-
|
|
281
|
-
if (this.animationSubscription) {
|
|
282
|
-
this.animationSubscription.unsubscribe();
|
|
283
|
-
}
|
|
346
|
+
this.clearAnimationControls();
|
|
284
347
|
|
|
285
348
|
this.animationSubscription =
|
|
286
349
|
this.animationCurrentIndex.observable.subscribe((index) => {
|
|
287
350
|
if (index >= finalNbTimes) {
|
|
288
|
-
this.
|
|
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
|
-
}
|
|
351
|
+
this.finishTemporaryAnimation();
|
|
299
352
|
}
|
|
300
353
|
});
|
|
354
|
+
|
|
355
|
+
if (finalNbTimes !== Infinity) {
|
|
356
|
+
this.animationResetTimeout = setTimeout(() => {
|
|
357
|
+
if (this.animationIsPlaying()) {
|
|
358
|
+
this.finishTemporaryAnimation();
|
|
359
|
+
}
|
|
360
|
+
}, restoreOptions?.timeoutMs ?? Math.max(1000, finalNbTimes * 1000));
|
|
361
|
+
}
|
|
362
|
+
|
|
301
363
|
this.animationName.set(animationName);
|
|
364
|
+
|
|
365
|
+
return waitPromise;
|
|
302
366
|
}
|
|
303
367
|
|
|
304
368
|
/**
|
|
@@ -306,10 +370,25 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
306
370
|
*
|
|
307
371
|
* @param id - Identifier of the component animation to play.
|
|
308
372
|
* @param params - Parameters forwarded to the animation effect.
|
|
373
|
+
* @returns A promise resolved when the animation component calls `onFinish`.
|
|
309
374
|
*/
|
|
310
|
-
showComponentAnimation(id: string, params: any) {
|
|
375
|
+
showComponentAnimation(id: string, params: any): Promise<void> {
|
|
311
376
|
const engine = inject(RpgClientEngine);
|
|
312
|
-
engine.getComponentAnimation(id).displayEffect(params, this);
|
|
377
|
+
return engine.getComponentAnimation(id).displayEffect(params, this);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Display a registered spritesheet animation effect on this object.
|
|
382
|
+
*
|
|
383
|
+
* @param graphic - Identifier of the spritesheet to use.
|
|
384
|
+
* @param animationName - Name of the animation inside the spritesheet.
|
|
385
|
+
* @returns A promise resolved when the animation component calls `onFinish`.
|
|
386
|
+
*/
|
|
387
|
+
showAnimation(graphic: string, animationName: string = 'default'): Promise<void> {
|
|
388
|
+
return this.showComponentAnimation('animation', {
|
|
389
|
+
graphic,
|
|
390
|
+
animationName,
|
|
391
|
+
});
|
|
313
392
|
}
|
|
314
393
|
|
|
315
394
|
/**
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
import { Hooks } from "@rpgjs/common";
|
|
3
|
+
import { ProjectileManager } from "./ProjectileManager";
|
|
4
|
+
|
|
5
|
+
describe("ProjectileManager", () => {
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
vi.useRealTimers();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("renders registered projectile components from compact spawn data", () => {
|
|
11
|
+
const onSpawn = vi.fn();
|
|
12
|
+
const hooks = new Hooks([{ projectiles: { onSpawn } }], "client");
|
|
13
|
+
const manager = new ProjectileManager(hooks);
|
|
14
|
+
const component = () => null;
|
|
15
|
+
|
|
16
|
+
manager.register("fireball", component);
|
|
17
|
+
manager.spawnBatch([
|
|
18
|
+
{
|
|
19
|
+
id: "p1",
|
|
20
|
+
type: "fireball",
|
|
21
|
+
origin: { x: 10, y: 20 },
|
|
22
|
+
direction: { x: 1, y: 0 },
|
|
23
|
+
speed: 100,
|
|
24
|
+
range: 500,
|
|
25
|
+
ttl: 5,
|
|
26
|
+
spawnTick: 1,
|
|
27
|
+
},
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
const current = manager.current();
|
|
31
|
+
expect(current).toHaveLength(1);
|
|
32
|
+
expect(current[0].component).toBe(component);
|
|
33
|
+
expect(current[0].props.x).toBeGreaterThanOrEqual(10);
|
|
34
|
+
expect(current[0].props.angle).toBe(0);
|
|
35
|
+
expect(onSpawn).toHaveBeenCalledWith(expect.objectContaining({ id: "p1", type: "fireball" }));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("starts visuals at the spawn origin even when a server tick estimate exists", () => {
|
|
39
|
+
vi.useFakeTimers();
|
|
40
|
+
vi.setSystemTime(2000);
|
|
41
|
+
|
|
42
|
+
const hooks = new Hooks([], "client");
|
|
43
|
+
const manager = new ProjectileManager(hooks);
|
|
44
|
+
manager.register("arrow", () => null);
|
|
45
|
+
manager.spawnBatch([
|
|
46
|
+
{
|
|
47
|
+
id: "p-latency",
|
|
48
|
+
type: "arrow",
|
|
49
|
+
origin: { x: 0, y: 0 },
|
|
50
|
+
direction: { x: 1, y: 0 },
|
|
51
|
+
speed: 120,
|
|
52
|
+
range: 500,
|
|
53
|
+
ttl: 5,
|
|
54
|
+
spawnTick: 10,
|
|
55
|
+
},
|
|
56
|
+
], {
|
|
57
|
+
currentServerTick: 16,
|
|
58
|
+
tickDurationMs: 1000 / 60,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const current = manager.current();
|
|
62
|
+
expect(current).toHaveLength(1);
|
|
63
|
+
expect(current[0].props.elapsed).toBeCloseTo(0, 3);
|
|
64
|
+
expect(current[0].props.x).toBeCloseTo(0, 3);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("keeps delayed projectiles until their visual delay has elapsed", () => {
|
|
68
|
+
vi.useFakeTimers();
|
|
69
|
+
vi.setSystemTime(1000);
|
|
70
|
+
|
|
71
|
+
const hooks = new Hooks([], "client");
|
|
72
|
+
const manager = new ProjectileManager(hooks);
|
|
73
|
+
manager.register("spark", () => null);
|
|
74
|
+
manager.spawnBatch([
|
|
75
|
+
{
|
|
76
|
+
id: "p-delayed",
|
|
77
|
+
type: "spark",
|
|
78
|
+
origin: { x: 0, y: 0 },
|
|
79
|
+
direction: { x: 1, y: 0 },
|
|
80
|
+
speed: 100,
|
|
81
|
+
range: 500,
|
|
82
|
+
ttl: 5,
|
|
83
|
+
spawnTick: 1,
|
|
84
|
+
delay: 0.1,
|
|
85
|
+
},
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
vi.setSystemTime(1050);
|
|
89
|
+
manager.step();
|
|
90
|
+
expect(manager.current()).toHaveLength(0);
|
|
91
|
+
|
|
92
|
+
vi.setSystemTime(1110);
|
|
93
|
+
manager.step();
|
|
94
|
+
const current = manager.current();
|
|
95
|
+
expect(current).toHaveLength(1);
|
|
96
|
+
expect(current[0].props.elapsed).toBeCloseTo(0.01, 3);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("keeps impacted projectiles briefly so components can react", () => {
|
|
100
|
+
const hooks = new Hooks([], "client");
|
|
101
|
+
const manager = new ProjectileManager(hooks);
|
|
102
|
+
manager.register("arrow", () => null);
|
|
103
|
+
manager.spawnBatch([
|
|
104
|
+
{
|
|
105
|
+
id: "p2",
|
|
106
|
+
type: "arrow",
|
|
107
|
+
origin: { x: 0, y: 0 },
|
|
108
|
+
direction: { x: 1, y: 0 },
|
|
109
|
+
speed: 100,
|
|
110
|
+
range: 500,
|
|
111
|
+
ttl: 5,
|
|
112
|
+
spawnTick: 1,
|
|
113
|
+
},
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
manager.impactBatch([{ id: "p2", x: 42, y: 0, distance: 42 }]);
|
|
117
|
+
|
|
118
|
+
const current = manager.current();
|
|
119
|
+
expect(current).toHaveLength(1);
|
|
120
|
+
expect(current[0].props.impact?.x).toBe(42);
|
|
121
|
+
expect(current[0].props.destroyed).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("keeps hit destroys briefly even if the destroy packet arrives before impact", () => {
|
|
125
|
+
const hooks = new Hooks([], "client");
|
|
126
|
+
const manager = new ProjectileManager(hooks);
|
|
127
|
+
manager.register("arrow", () => null);
|
|
128
|
+
manager.spawnBatch([
|
|
129
|
+
{
|
|
130
|
+
id: "p3",
|
|
131
|
+
type: "arrow",
|
|
132
|
+
origin: { x: 0, y: 0 },
|
|
133
|
+
direction: { x: 1, y: 0 },
|
|
134
|
+
speed: 100,
|
|
135
|
+
range: 500,
|
|
136
|
+
ttl: 5,
|
|
137
|
+
spawnTick: 1,
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
manager.destroyBatch([{ id: "p3", reason: "hit", x: 48, y: 0, distance: 48 }]);
|
|
142
|
+
|
|
143
|
+
const current = manager.current();
|
|
144
|
+
expect(current).toHaveLength(1);
|
|
145
|
+
expect(current[0].props.impact?.x).toBe(48);
|
|
146
|
+
expect(current[0].props.destroyed).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("freezes hit destroys at the authoritative impact position until the impact completes", () => {
|
|
150
|
+
vi.useFakeTimers();
|
|
151
|
+
vi.setSystemTime(1000);
|
|
152
|
+
|
|
153
|
+
const hooks = new Hooks([], "client");
|
|
154
|
+
const manager = new ProjectileManager(hooks);
|
|
155
|
+
manager.register("arrow", () => null);
|
|
156
|
+
manager.spawnBatch([
|
|
157
|
+
{
|
|
158
|
+
id: "p4",
|
|
159
|
+
type: "arrow",
|
|
160
|
+
origin: { x: 0, y: 0 },
|
|
161
|
+
direction: { x: 1, y: 0 },
|
|
162
|
+
speed: 100,
|
|
163
|
+
range: 500,
|
|
164
|
+
ttl: 5,
|
|
165
|
+
spawnTick: 1,
|
|
166
|
+
},
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
vi.setSystemTime(1200);
|
|
170
|
+
manager.destroyBatch([{ id: "p4", reason: "hit", x: 48, y: 0, distance: 48 }]);
|
|
171
|
+
|
|
172
|
+
vi.setSystemTime(1300);
|
|
173
|
+
manager.step();
|
|
174
|
+
let current = manager.current();
|
|
175
|
+
expect(current).toHaveLength(1);
|
|
176
|
+
expect(current[0].props.x).toBe(48);
|
|
177
|
+
expect(current[0].props.distance).toBe(48);
|
|
178
|
+
expect(current[0].props.impactProgress).toBeCloseTo(100 / 350, 3);
|
|
179
|
+
|
|
180
|
+
vi.setSystemTime(1600);
|
|
181
|
+
manager.step();
|
|
182
|
+
current = manager.current();
|
|
183
|
+
expect(current).toHaveLength(0);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("clamps visual movement at the predicted impact without starting the impact animation", () => {
|
|
187
|
+
vi.useFakeTimers();
|
|
188
|
+
vi.setSystemTime(1000);
|
|
189
|
+
|
|
190
|
+
const hooks = new Hooks([], "client");
|
|
191
|
+
const manager = new ProjectileManager(hooks, () => ({
|
|
192
|
+
id: "p5",
|
|
193
|
+
targetId: "target",
|
|
194
|
+
x: 30,
|
|
195
|
+
y: 0,
|
|
196
|
+
distance: 30,
|
|
197
|
+
}));
|
|
198
|
+
manager.register("arrow", () => null);
|
|
199
|
+
manager.spawnBatch([
|
|
200
|
+
{
|
|
201
|
+
id: "p5",
|
|
202
|
+
type: "arrow",
|
|
203
|
+
origin: { x: 0, y: 0 },
|
|
204
|
+
direction: { x: 1, y: 0 },
|
|
205
|
+
speed: 100,
|
|
206
|
+
range: 500,
|
|
207
|
+
ttl: 5,
|
|
208
|
+
spawnTick: 1,
|
|
209
|
+
},
|
|
210
|
+
]);
|
|
211
|
+
|
|
212
|
+
vi.setSystemTime(1200);
|
|
213
|
+
manager.step();
|
|
214
|
+
let current = manager.current();
|
|
215
|
+
expect(current).toHaveLength(1);
|
|
216
|
+
expect(current[0].props.x).toBe(20);
|
|
217
|
+
expect(current[0].props.impact).toBeUndefined();
|
|
218
|
+
expect(current[0].props.destroyed).toBe(false);
|
|
219
|
+
|
|
220
|
+
vi.setSystemTime(1400);
|
|
221
|
+
manager.step();
|
|
222
|
+
current = manager.current();
|
|
223
|
+
expect(current).toHaveLength(1);
|
|
224
|
+
expect(current[0].props.x).toBe(30);
|
|
225
|
+
expect(current[0].props.distance).toBe(30);
|
|
226
|
+
expect(current[0].props.impact).toBeUndefined();
|
|
227
|
+
expect(current[0].props.destroyed).toBe(false);
|
|
228
|
+
|
|
229
|
+
manager.impactBatch([{ id: "p5", targetId: "target", x: 32, y: 0, distance: 32 }]);
|
|
230
|
+
current = manager.current();
|
|
231
|
+
expect(current[0].props.x).toBe(30);
|
|
232
|
+
expect(current[0].props.distance).toBe(30);
|
|
233
|
+
expect(current[0].props.impact?.x).toBe(32);
|
|
234
|
+
expect(current[0].props.destroyed).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("uses the authoritative impact position when the predicted target differs", () => {
|
|
238
|
+
vi.useFakeTimers();
|
|
239
|
+
vi.setSystemTime(1000);
|
|
240
|
+
|
|
241
|
+
const hooks = new Hooks([], "client");
|
|
242
|
+
const manager = new ProjectileManager(hooks, () => ({
|
|
243
|
+
id: "p7",
|
|
244
|
+
targetId: "wall",
|
|
245
|
+
x: 30,
|
|
246
|
+
y: 0,
|
|
247
|
+
distance: 30,
|
|
248
|
+
}));
|
|
249
|
+
manager.register("arrow", () => null);
|
|
250
|
+
manager.spawnBatch([
|
|
251
|
+
{
|
|
252
|
+
id: "p7",
|
|
253
|
+
type: "arrow",
|
|
254
|
+
origin: { x: 0, y: 0 },
|
|
255
|
+
direction: { x: 1, y: 0 },
|
|
256
|
+
speed: 100,
|
|
257
|
+
range: 500,
|
|
258
|
+
ttl: 5,
|
|
259
|
+
spawnTick: 1,
|
|
260
|
+
},
|
|
261
|
+
]);
|
|
262
|
+
|
|
263
|
+
vi.setSystemTime(1400);
|
|
264
|
+
manager.step();
|
|
265
|
+
manager.impactBatch([{ id: "p7", targetId: "target", x: 45, y: 0, distance: 45 }]);
|
|
266
|
+
|
|
267
|
+
const current = manager.current();
|
|
268
|
+
expect(current[0].props.x).toBe(45);
|
|
269
|
+
expect(current[0].props.distance).toBe(45);
|
|
270
|
+
expect(current[0].props.impact?.x).toBe(45);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("keeps an unconfirmed predicted impact clamped until the server resolves it", () => {
|
|
274
|
+
vi.useFakeTimers();
|
|
275
|
+
vi.setSystemTime(1000);
|
|
276
|
+
|
|
277
|
+
const hooks = new Hooks([], "client");
|
|
278
|
+
const manager = new ProjectileManager(hooks, () => ({
|
|
279
|
+
id: "p6",
|
|
280
|
+
targetId: "ignored",
|
|
281
|
+
x: 30,
|
|
282
|
+
y: 0,
|
|
283
|
+
distance: 30,
|
|
284
|
+
}));
|
|
285
|
+
manager.register("arrow", () => null);
|
|
286
|
+
manager.spawnBatch([
|
|
287
|
+
{
|
|
288
|
+
id: "p6",
|
|
289
|
+
type: "arrow",
|
|
290
|
+
origin: { x: 0, y: 0 },
|
|
291
|
+
direction: { x: 1, y: 0 },
|
|
292
|
+
speed: 100,
|
|
293
|
+
range: 500,
|
|
294
|
+
ttl: 5,
|
|
295
|
+
spawnTick: 1,
|
|
296
|
+
},
|
|
297
|
+
]);
|
|
298
|
+
|
|
299
|
+
vi.setSystemTime(1400);
|
|
300
|
+
manager.step();
|
|
301
|
+
expect(manager.current()[0].props.x).toBe(30);
|
|
302
|
+
|
|
303
|
+
vi.setSystemTime(1900);
|
|
304
|
+
manager.step();
|
|
305
|
+
const current = manager.current();
|
|
306
|
+
expect(current).toHaveLength(1);
|
|
307
|
+
expect(current[0].props.x).toBe(30);
|
|
308
|
+
expect(current[0].props.impact).toBeUndefined();
|
|
309
|
+
expect(current[0].props.destroyed).toBe(false);
|
|
310
|
+
|
|
311
|
+
manager.destroyBatch([{ id: "p6", reason: "range" }]);
|
|
312
|
+
expect(manager.current()[0].props.x).toBe(30);
|
|
313
|
+
expect(manager.current()[0].props.destroyed).toBe(true);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("skips local impact prediction when the server marks the projectile as non-predictable", () => {
|
|
317
|
+
const hooks = new Hooks([], "client");
|
|
318
|
+
const predictionResolver = vi.fn();
|
|
319
|
+
const manager = new ProjectileManager(hooks, predictionResolver);
|
|
320
|
+
manager.register("arrow", () => null);
|
|
321
|
+
|
|
322
|
+
manager.spawnBatch([
|
|
323
|
+
{
|
|
324
|
+
id: "p-no-predict",
|
|
325
|
+
type: "arrow",
|
|
326
|
+
origin: { x: 0, y: 0 },
|
|
327
|
+
direction: { x: 1, y: 0 },
|
|
328
|
+
speed: 100,
|
|
329
|
+
range: 500,
|
|
330
|
+
ttl: 5,
|
|
331
|
+
spawnTick: 1,
|
|
332
|
+
predictImpact: false,
|
|
333
|
+
},
|
|
334
|
+
]);
|
|
335
|
+
|
|
336
|
+
expect(predictionResolver).not.toHaveBeenCalled();
|
|
337
|
+
});
|
|
338
|
+
});
|