@rpgjs/client 5.0.0-beta.11 → 5.0.0-beta.13
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 +19 -0
- package/dist/Game/AnimationManager.d.ts +1 -0
- package/dist/Game/AnimationManager.js +3 -0
- package/dist/Game/AnimationManager.js.map +1 -1
- package/dist/Game/ClientVisuals.d.ts +61 -0
- package/dist/Game/ClientVisuals.js +96 -0
- package/dist/Game/ClientVisuals.js.map +1 -0
- package/dist/Game/ClientVisuals.spec.d.ts +1 -0
- package/dist/Game/EventComponentResolver.d.ts +16 -0
- package/dist/Game/EventComponentResolver.js +52 -0
- package/dist/Game/EventComponentResolver.js.map +1 -0
- package/dist/Game/EventComponentResolver.spec.d.ts +1 -0
- package/dist/Game/Map.js +9 -0
- package/dist/Game/Map.js.map +1 -1
- package/dist/Game/Object.d.ts +2 -0
- package/dist/Game/Object.js +22 -8
- package/dist/Game/Object.js.map +1 -1
- package/dist/Game/Object.spec.d.ts +1 -0
- package/dist/Game/ProjectileManager.d.ts +11 -2
- package/dist/Game/ProjectileManager.js +19 -2
- package/dist/Game/ProjectileManager.js.map +1 -1
- package/dist/Gui/Gui.d.ts +3 -2
- package/dist/Gui/Gui.js +18 -6
- package/dist/Gui/Gui.js.map +1 -1
- package/dist/RpgClient.d.ts +85 -1
- package/dist/RpgClientEngine.d.ts +77 -2
- package/dist/RpgClientEngine.js +290 -31
- package/dist/RpgClientEngine.js.map +1 -1
- package/dist/components/animations/fx.ce.js +58 -0
- package/dist/components/animations/fx.ce.js.map +1 -0
- package/dist/components/animations/index.d.ts +1 -0
- package/dist/components/animations/index.js +3 -1
- package/dist/components/animations/index.js.map +1 -1
- package/dist/components/character.ce.js +192 -19
- package/dist/components/character.ce.js.map +1 -1
- package/dist/components/gui/dialogbox/index.ce.js +27 -12
- package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
- package/dist/components/gui/gameover.ce.js +4 -3
- package/dist/components/gui/gameover.ce.js.map +1 -1
- package/dist/components/gui/menu/equip-menu.ce.js +9 -8
- package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/exit-menu.ce.js +7 -5
- package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/items-menu.ce.js +8 -7
- package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/main-menu.ce.js +12 -11
- package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/options-menu.ce.js +7 -5
- package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/skills-menu.ce.js +4 -2
- package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
- package/dist/components/gui/notification/notification.ce.js +4 -1
- package/dist/components/gui/notification/notification.ce.js.map +1 -1
- package/dist/components/gui/save-load.ce.js +10 -9
- package/dist/components/gui/save-load.ce.js.map +1 -1
- package/dist/components/gui/shop/shop.ce.js +17 -16
- package/dist/components/gui/shop/shop.ce.js.map +1 -1
- package/dist/components/gui/title-screen.ce.js +4 -3
- package/dist/components/gui/title-screen.ce.js.map +1 -1
- package/dist/components/interaction-components.ce.js +20 -0
- package/dist/components/interaction-components.ce.js.map +1 -0
- package/dist/components/scenes/canvas.ce.js +12 -7
- package/dist/components/scenes/canvas.ce.js.map +1 -1
- package/dist/components/scenes/draw-map.ce.js +18 -13
- package/dist/components/scenes/draw-map.ce.js.map +1 -1
- package/dist/i18n.d.ts +55 -0
- package/dist/i18n.js +60 -0
- package/dist/i18n.js.map +1 -0
- package/dist/i18n.spec.d.ts +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -2
- package/dist/module.js +30 -3
- package/dist/module.js.map +1 -1
- package/dist/services/actionInput.d.ts +3 -1
- package/dist/services/actionInput.js +33 -1
- package/dist/services/actionInput.js.map +1 -1
- package/dist/services/interactions.d.ts +159 -0
- package/dist/services/interactions.js +460 -0
- package/dist/services/interactions.js.map +1 -0
- package/dist/services/interactions.spec.d.ts +1 -0
- package/dist/services/keyboardControls.d.ts +1 -0
- package/dist/services/keyboardControls.js +1 -0
- package/dist/services/keyboardControls.js.map +1 -1
- package/dist/services/standalone.d.ts +3 -1
- package/dist/services/standalone.js +31 -13
- package/dist/services/standalone.js.map +1 -1
- package/dist/utils/mapId.d.ts +1 -0
- package/dist/utils/mapId.js +6 -0
- package/dist/utils/mapId.js.map +1 -0
- package/package.json +4 -4
- package/src/Game/AnimationManager.ts +4 -0
- package/src/Game/ClientVisuals.spec.ts +56 -0
- package/src/Game/ClientVisuals.ts +184 -0
- package/src/Game/EventComponentResolver.spec.ts +84 -0
- package/src/Game/EventComponentResolver.ts +74 -0
- package/src/Game/Map.ts +10 -0
- package/src/Game/Object.spec.ts +59 -0
- package/src/Game/Object.ts +36 -12
- package/src/Game/ProjectileManager.spec.ts +111 -0
- package/src/Game/ProjectileManager.ts +24 -2
- package/src/Gui/Gui.spec.ts +67 -0
- package/src/Gui/Gui.ts +24 -7
- package/src/RpgClient.ts +96 -1
- package/src/RpgClientEngine.ts +378 -45
- package/src/components/animations/fx.ce +101 -0
- package/src/components/animations/index.ts +4 -2
- package/src/components/character.ce +243 -17
- package/src/components/gui/dialogbox/index.ce +35 -14
- package/src/components/gui/gameover.ce +4 -3
- package/src/components/gui/menu/equip-menu.ce +9 -8
- package/src/components/gui/menu/exit-menu.ce +4 -3
- package/src/components/gui/menu/items-menu.ce +8 -7
- package/src/components/gui/menu/main-menu.ce +12 -11
- package/src/components/gui/menu/options-menu.ce +4 -3
- package/src/components/gui/menu/skills-menu.ce +2 -1
- package/src/components/gui/notification/notification.ce +7 -1
- package/src/components/gui/save-load.ce +11 -10
- package/src/components/gui/shop/shop.ce +17 -16
- package/src/components/gui/title-screen.ce +4 -3
- package/src/components/interaction-components.ce +23 -0
- package/src/components/scenes/canvas.ce +12 -7
- package/src/components/scenes/draw-map.ce +16 -5
- package/src/i18n.spec.ts +39 -0
- package/src/i18n.ts +59 -0
- package/src/index.ts +3 -0
- package/src/module.ts +43 -10
- package/src/services/actionInput.spec.ts +54 -0
- package/src/services/actionInput.ts +68 -1
- package/src/services/interactions.spec.ts +175 -0
- package/src/services/interactions.ts +722 -0
- package/src/services/keyboardControls.ts +2 -1
- package/src/services/standalone.ts +39 -10
- package/src/utils/mapId.ts +2 -0
package/src/RpgClientEngine.ts
CHANGED
|
@@ -6,7 +6,9 @@ import { AbstractWebsocket, WebSocketToken } from "./services/AbstractSocket";
|
|
|
6
6
|
import { LoadMapService, LoadMapToken } from "./services/loadMap";
|
|
7
7
|
import { RpgSound } from "./Sound";
|
|
8
8
|
import { RpgResource } from "./Resource";
|
|
9
|
-
import { Hooks, ModulesToken, Direction, normalizeLightingState, Vector2 } from "@rpgjs/common";
|
|
9
|
+
import { getOrCreateI18nService, Hooks, ModulesToken, Direction, normalizeLightingState, Vector2, type I18nParams, type I18nService } from "@rpgjs/common";
|
|
10
|
+
import type { EventComponentConfig } from "./RpgClient";
|
|
11
|
+
import type { RpgClientEvent } from "./Game/Event";
|
|
10
12
|
import { load } from "@signe/sync";
|
|
11
13
|
import { RpgClientMap } from "./Game/Map"
|
|
12
14
|
import { RpgGui } from "./Gui/Gui";
|
|
@@ -25,24 +27,107 @@ import {
|
|
|
25
27
|
type PredictionState,
|
|
26
28
|
type RpgActionInput,
|
|
27
29
|
type RpgActionName,
|
|
30
|
+
type RpgDashInput,
|
|
31
|
+
type RpgMovementInput,
|
|
28
32
|
} from "@rpgjs/common";
|
|
29
33
|
import { NotificationManager } from "./Gui/NotificationManager";
|
|
30
34
|
import { SaveClientService } from "./services/save";
|
|
31
35
|
import { getCanMoveValue } from "./utils/readPropValue";
|
|
32
36
|
import { ProjectileManager, type ClientProjectileImpact, type ClientProjectileSpawn } from "./Game/ProjectileManager";
|
|
37
|
+
import { ClientVisualRegistry, type ClientVisualHandler, type ClientVisualMap, type ClientVisualPacket } from "./Game/ClientVisuals";
|
|
33
38
|
import { normalizeActionInput } from "./services/actionInput";
|
|
34
39
|
import { createClientPointerContext, type ClientPointerContext } from "./services/pointerContext";
|
|
40
|
+
import { RpgClientInteractions } from "./services/interactions";
|
|
41
|
+
import { normalizeRoomMapId } from "./utils/mapId";
|
|
42
|
+
import { EventComponentResolverRegistry, type EventComponentResolver } from "./Game/EventComponentResolver";
|
|
43
|
+
import { RpgClientBuiltinI18n } from "./i18n";
|
|
35
44
|
|
|
36
45
|
interface MovementTrajectoryPoint {
|
|
37
46
|
frame: number;
|
|
38
47
|
tick: number;
|
|
39
48
|
timestamp: number;
|
|
40
|
-
input:
|
|
49
|
+
input: RpgMovementInput;
|
|
41
50
|
x: number;
|
|
42
51
|
y: number;
|
|
43
52
|
direction?: Direction;
|
|
44
53
|
}
|
|
45
54
|
|
|
55
|
+
const DEFAULT_DASH_ADDITIONAL_SPEED = 8;
|
|
56
|
+
const DEFAULT_DASH_DURATION_MS = 180;
|
|
57
|
+
const DEFAULT_DASH_COOLDOWN_MS = 450;
|
|
58
|
+
|
|
59
|
+
const isDashInput = (input: RpgMovementInput): input is RpgDashInput =>
|
|
60
|
+
typeof input === "object" && input !== null && input.type === "dash";
|
|
61
|
+
|
|
62
|
+
const isMoveInput = (
|
|
63
|
+
input: RpgMovementInput
|
|
64
|
+
): input is { type: "move"; direction: Direction } =>
|
|
65
|
+
typeof input === "object" && input !== null && input.type === "move";
|
|
66
|
+
|
|
67
|
+
const resolveMoveDirection = (input: RpgMovementInput): Direction | undefined => {
|
|
68
|
+
if (isMoveInput(input)) return input.direction;
|
|
69
|
+
if (typeof input === "string" || typeof input === "number") {
|
|
70
|
+
return input as Direction;
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const directionToVector = (direction: Direction | undefined) => {
|
|
76
|
+
switch (direction) {
|
|
77
|
+
case Direction.Left:
|
|
78
|
+
return { x: -1, y: 0 };
|
|
79
|
+
case Direction.Right:
|
|
80
|
+
return { x: 1, y: 0 };
|
|
81
|
+
case Direction.Up:
|
|
82
|
+
return { x: 0, y: -1 };
|
|
83
|
+
case Direction.Down:
|
|
84
|
+
default:
|
|
85
|
+
return { x: 0, y: 1 };
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const vectorToDirection = (direction: { x: number; y: number }): Direction => {
|
|
90
|
+
if (Math.abs(direction.x) > Math.abs(direction.y)) {
|
|
91
|
+
return direction.x < 0 ? Direction.Left : Direction.Right;
|
|
92
|
+
}
|
|
93
|
+
return direction.y < 0 ? Direction.Up : Direction.Down;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const normalizeDashInput = (
|
|
97
|
+
input: Partial<RpgDashInput>,
|
|
98
|
+
fallbackDirection: Direction | undefined
|
|
99
|
+
): RpgDashInput | null => {
|
|
100
|
+
const rawDirection = input.direction ?? directionToVector(fallbackDirection);
|
|
101
|
+
const rawX = Number(rawDirection?.x ?? 0);
|
|
102
|
+
const rawY = Number(rawDirection?.y ?? 0);
|
|
103
|
+
const magnitude = Math.hypot(rawX, rawY);
|
|
104
|
+
if (!Number.isFinite(magnitude) || magnitude <= 0) return null;
|
|
105
|
+
|
|
106
|
+
const additionalSpeed =
|
|
107
|
+
typeof input.additionalSpeed === "number" && Number.isFinite(input.additionalSpeed)
|
|
108
|
+
? Math.max(0, Math.min(input.additionalSpeed, 64))
|
|
109
|
+
: DEFAULT_DASH_ADDITIONAL_SPEED;
|
|
110
|
+
const duration =
|
|
111
|
+
typeof input.duration === "number" && Number.isFinite(input.duration)
|
|
112
|
+
? Math.max(1, Math.min(input.duration, 1000))
|
|
113
|
+
: DEFAULT_DASH_DURATION_MS;
|
|
114
|
+
const cooldown =
|
|
115
|
+
typeof input.cooldown === "number" && Number.isFinite(input.cooldown)
|
|
116
|
+
? Math.max(0, Math.min(input.cooldown, 5000))
|
|
117
|
+
: DEFAULT_DASH_COOLDOWN_MS;
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
type: "dash",
|
|
121
|
+
direction: {
|
|
122
|
+
x: rawX / magnitude,
|
|
123
|
+
y: rawY / magnitude,
|
|
124
|
+
},
|
|
125
|
+
additionalSpeed,
|
|
126
|
+
duration,
|
|
127
|
+
cooldown,
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
46
131
|
type ConfigurableTrigger<T> = Omit<Trigger<T>, "start"> & {
|
|
47
132
|
start(config?: T): Promise<void>;
|
|
48
133
|
};
|
|
@@ -68,10 +153,13 @@ export class RpgClientEngine<T = any> {
|
|
|
68
153
|
width = signal("100%");
|
|
69
154
|
height = signal("100%");
|
|
70
155
|
spritesheets: Map<string | number, any> = new Map();
|
|
156
|
+
private spritesheetPromises: Map<string | number, Promise<any>> = new Map();
|
|
71
157
|
sounds: Map<string, any> = new Map();
|
|
72
158
|
componentAnimations: any[] = [];
|
|
159
|
+
clientVisuals = new ClientVisualRegistry();
|
|
73
160
|
projectiles: ProjectileManager;
|
|
74
161
|
pointer: ClientPointerContext = createClientPointerContext();
|
|
162
|
+
interactions: RpgClientInteractions = new RpgClientInteractions(this);
|
|
75
163
|
private spritesheetResolver?: (id: string | number) => any | Promise<any>;
|
|
76
164
|
private soundResolver?: (id: string) => any | Promise<any>;
|
|
77
165
|
particleSettings: {
|
|
@@ -87,6 +175,7 @@ export class RpgClientEngine<T = any> {
|
|
|
87
175
|
spriteComponentsBehind = signal<any[]>([]);
|
|
88
176
|
spriteComponentsInFront = signal<any[]>([]);
|
|
89
177
|
spriteComponents: Map<string, any> = new Map();
|
|
178
|
+
private eventComponentResolvers = new EventComponentResolverRegistry();
|
|
90
179
|
/** ID of the sprite that the camera should follow. null means follow the current player */
|
|
91
180
|
cameraFollowTargetId = signal<string | null>(null);
|
|
92
181
|
/** Trigger for map shake animation */
|
|
@@ -96,7 +185,7 @@ export class RpgClientEngine<T = any> {
|
|
|
96
185
|
gamePause = signal(false);
|
|
97
186
|
|
|
98
187
|
private predictionEnabled = false;
|
|
99
|
-
private prediction?: PredictionController<Direction>;
|
|
188
|
+
private prediction?: PredictionController<RpgMovementInput, Direction>;
|
|
100
189
|
private readonly SERVER_CORRECTION_THRESHOLD = 30;
|
|
101
190
|
private inputFrameCounter = 0;
|
|
102
191
|
private pendingPredictionFrames: number[] = [];
|
|
@@ -104,6 +193,7 @@ export class RpgClientEngine<T = any> {
|
|
|
104
193
|
private frameOffset = 0;
|
|
105
194
|
private latestServerTick?: number;
|
|
106
195
|
private latestServerTickAt = 0;
|
|
196
|
+
private dashLockedUntil = 0;
|
|
107
197
|
// Ping/Pong for RTT measurement
|
|
108
198
|
private rtt: number = 0; // Round-trip time in ms
|
|
109
199
|
private pingInterval: any = null;
|
|
@@ -120,20 +210,29 @@ export class RpgClientEngine<T = any> {
|
|
|
120
210
|
private eventsReceived$ = new BehaviorSubject<boolean>(false);
|
|
121
211
|
private onAfterLoadingSubscription?: any;
|
|
122
212
|
private sceneResetQueued = false;
|
|
213
|
+
private mapTransitionInProgress = false;
|
|
214
|
+
private currentMapRoomId?: string;
|
|
215
|
+
private socketListenersInitialized = false;
|
|
123
216
|
|
|
124
217
|
// Store subscriptions and event listeners for cleanup
|
|
125
218
|
private tickSubscriptions: any[] = [];
|
|
126
219
|
private resizeHandler?: () => void;
|
|
127
220
|
private pointerMoveHandler?: (event: PointerEvent) => void;
|
|
221
|
+
private pointerUpHandler?: (event: PointerEvent) => void;
|
|
222
|
+
private pointerCancelHandler?: (event: PointerEvent) => void;
|
|
128
223
|
private pointerCanvas?: HTMLCanvasElement;
|
|
129
224
|
private pendingSyncPackets: any[] = [];
|
|
130
225
|
private notificationManager: NotificationManager = new NotificationManager();
|
|
226
|
+
private i18nService: I18nService;
|
|
227
|
+
private locale?: string;
|
|
131
228
|
|
|
132
229
|
constructor(public context) {
|
|
133
230
|
this.webSocket = inject(WebSocketToken);
|
|
134
231
|
this.guiService = inject(RpgGui);
|
|
135
232
|
this.loadMapService = inject(LoadMapToken);
|
|
136
233
|
this.hooks = inject<Hooks>(ModulesToken);
|
|
234
|
+
this.i18nService = getOrCreateI18nService(context);
|
|
235
|
+
this.i18nService.addMessages(RpgClientBuiltinI18n, "rpgjs-client", 0);
|
|
137
236
|
this.projectiles = new ProjectileManager(
|
|
138
237
|
this.hooks,
|
|
139
238
|
(projectile) => this.predictProjectileImpact(projectile),
|
|
@@ -169,6 +268,25 @@ export class RpgClientEngine<T = any> {
|
|
|
169
268
|
this.initializePredictionController();
|
|
170
269
|
}
|
|
171
270
|
|
|
271
|
+
setLocale(locale: string) {
|
|
272
|
+
this.locale = locale;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
getLocale(): string {
|
|
276
|
+
return this.locale || this.i18nService.defaultLocale;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
t(key: string, params?: I18nParams): string {
|
|
280
|
+
return this.i18nService.t(key, params, this.getLocale());
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
i18n() {
|
|
284
|
+
return {
|
|
285
|
+
locale: this.getLocale(),
|
|
286
|
+
t: (key: string, params?: I18nParams) => this.t(key, params),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
172
290
|
/**
|
|
173
291
|
* Assigns a CanvasEngine KeyboardControls instance to the dependency injection context
|
|
174
292
|
*
|
|
@@ -244,7 +362,6 @@ export class RpgClientEngine<T = any> {
|
|
|
244
362
|
this.renderer = app.renderer as unknown as PIXI.Renderer;
|
|
245
363
|
this.setupPointerTracking();
|
|
246
364
|
this.tick = canvasElement?.propObservables?.context['tick'].observable
|
|
247
|
-
this.flushPendingSyncPackets();
|
|
248
365
|
|
|
249
366
|
const inputCheckSubscription = this.tick.subscribe(() => {
|
|
250
367
|
if (Date.now() - this.lastInputTime > 100) {
|
|
@@ -258,6 +375,7 @@ export class RpgClientEngine<T = any> {
|
|
|
258
375
|
|
|
259
376
|
this.hooks.callHooks("client-spritesheets-load", this).subscribe();
|
|
260
377
|
this.hooks.callHooks("client-spritesheetResolver-load", this).subscribe();
|
|
378
|
+
this.flushPendingSyncPackets();
|
|
261
379
|
this.hooks.callHooks("client-sounds-load", this).subscribe();
|
|
262
380
|
this.hooks.callHooks("client-soundResolver-load", this).subscribe();
|
|
263
381
|
|
|
@@ -266,7 +384,9 @@ export class RpgClientEngine<T = any> {
|
|
|
266
384
|
this.hooks.callHooks("client-gui-load", this).subscribe();
|
|
267
385
|
this.hooks.callHooks("client-particles-load", this).subscribe();
|
|
268
386
|
this.hooks.callHooks("client-componentAnimations-load", this).subscribe();
|
|
387
|
+
this.hooks.callHooks("client-clientVisuals-load", this).subscribe();
|
|
269
388
|
this.hooks.callHooks("client-projectiles-load", this).subscribe();
|
|
389
|
+
this.hooks.callHooks("client-interactions-load", this).subscribe();
|
|
270
390
|
this.hooks.callHooks("client-sprite-load", this).subscribe();
|
|
271
391
|
|
|
272
392
|
await lastValueFrom(this.hooks.callHooks("client-engine-onStart", this));
|
|
@@ -313,7 +433,7 @@ export class RpgClientEngine<T = any> {
|
|
|
313
433
|
}
|
|
314
434
|
|
|
315
435
|
this.pointerCanvas = canvas;
|
|
316
|
-
|
|
436
|
+
const updatePointer = (event: PointerEvent) => {
|
|
317
437
|
const rect = canvas.getBoundingClientRect();
|
|
318
438
|
const screen = {
|
|
319
439
|
x: event.clientX - rect.left,
|
|
@@ -333,16 +453,67 @@ export class RpgClientEngine<T = any> {
|
|
|
333
453
|
this.pointer.update(screen, world);
|
|
334
454
|
};
|
|
335
455
|
|
|
456
|
+
this.pointerMoveHandler = (event: PointerEvent) => {
|
|
457
|
+
updatePointer(event);
|
|
458
|
+
this.interactions.handlePointerMove(event);
|
|
459
|
+
};
|
|
460
|
+
this.pointerUpHandler = (event: PointerEvent) => {
|
|
461
|
+
updatePointer(event);
|
|
462
|
+
this.interactions.handlePointerUp(event);
|
|
463
|
+
};
|
|
464
|
+
this.pointerCancelHandler = (event: PointerEvent) => {
|
|
465
|
+
updatePointer(event);
|
|
466
|
+
this.interactions.cancelDrag(event);
|
|
467
|
+
};
|
|
468
|
+
|
|
336
469
|
canvas.addEventListener("pointermove", this.pointerMoveHandler);
|
|
337
470
|
canvas.addEventListener("pointerdown", this.pointerMoveHandler);
|
|
471
|
+
canvas.addEventListener("pointerup", this.pointerUpHandler);
|
|
472
|
+
canvas.addEventListener("pointercancel", this.pointerCancelHandler);
|
|
473
|
+
canvas.addEventListener("pointerleave", this.pointerCancelHandler);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
updatePointerFromInteractionEvent(event: any): void {
|
|
477
|
+
const global = event?.global ?? event?.data?.global;
|
|
478
|
+
|
|
479
|
+
if (!global) {
|
|
480
|
+
this.pointer.updateFromEvent(event);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const screen = {
|
|
485
|
+
x: Number(global.x),
|
|
486
|
+
y: Number(global.y),
|
|
487
|
+
};
|
|
488
|
+
if (!Number.isFinite(screen.x) || !Number.isFinite(screen.y)) {
|
|
489
|
+
this.pointer.updateFromEvent(event);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const viewport = this.findViewportInstance();
|
|
494
|
+
if (viewport && typeof viewport.toWorld === "function") {
|
|
495
|
+
const point = viewport.toWorld(screen.x, screen.y);
|
|
496
|
+
this.pointer.update(screen, { x: Number(point.x), y: Number(point.y) });
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
this.pointer.update(screen);
|
|
338
501
|
}
|
|
339
502
|
|
|
340
503
|
private findViewportInstance(): any {
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
typeof
|
|
344
|
-
|
|
345
|
-
|
|
504
|
+
const find = (node: any): any => {
|
|
505
|
+
if (!node) return undefined;
|
|
506
|
+
if (typeof node?.toWorld === "function" || node?.constructor?.name === "Viewport") {
|
|
507
|
+
return node;
|
|
508
|
+
}
|
|
509
|
+
for (const child of node.children ?? []) {
|
|
510
|
+
const viewport = find(child);
|
|
511
|
+
if (viewport) return viewport;
|
|
512
|
+
}
|
|
513
|
+
return undefined;
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
return find((this.canvasApp as any)?.stage);
|
|
346
517
|
}
|
|
347
518
|
|
|
348
519
|
private prepareSyncPayload(data: any): any {
|
|
@@ -392,6 +563,9 @@ export class RpgClientEngine<T = any> {
|
|
|
392
563
|
}
|
|
393
564
|
|
|
394
565
|
private initListeners() {
|
|
566
|
+
if (this.socketListenersInitialized) return;
|
|
567
|
+
this.socketListenersInitialized = true;
|
|
568
|
+
|
|
395
569
|
this.webSocket.on("sync", (data) => {
|
|
396
570
|
if (!this.tick) {
|
|
397
571
|
this.pendingSyncPackets.push(data);
|
|
@@ -420,13 +594,8 @@ export class RpgClientEngine<T = any> {
|
|
|
420
594
|
});
|
|
421
595
|
|
|
422
596
|
this.webSocket.on("changeMap", (data) => {
|
|
423
|
-
|
|
424
|
-
this.
|
|
425
|
-
this.sceneMap.lightingState.set(null);
|
|
426
|
-
this.sceneMap.clearLightSpots();
|
|
427
|
-
this.projectiles.clear();
|
|
428
|
-
// Reset camera follow to default (follow current player) when changing maps
|
|
429
|
-
this.cameraFollowTargetId.set(null);
|
|
597
|
+
const nextMapId = typeof data?.mapId === "string" ? data.mapId : undefined;
|
|
598
|
+
this.beginMapTransfer(nextMapId);
|
|
430
599
|
const transferToken = typeof data?.transferToken === "string" ? data.transferToken : undefined;
|
|
431
600
|
this.loadScene(data.mapId, transferToken);
|
|
432
601
|
});
|
|
@@ -440,22 +609,35 @@ export class RpgClientEngine<T = any> {
|
|
|
440
609
|
this.getComponentAnimation(id).displayEffect(params, player || position)
|
|
441
610
|
});
|
|
442
611
|
|
|
612
|
+
this.webSocket.on("clientVisual", (data) => {
|
|
613
|
+
this.playClientVisual(data);
|
|
614
|
+
});
|
|
615
|
+
|
|
443
616
|
this.webSocket.on("projectile:spawnBatch", (data) => {
|
|
617
|
+
if (!this.shouldProcessProjectilePacket(data)) return;
|
|
444
618
|
this.projectiles.spawnBatch(data?.projectiles ?? [], {
|
|
619
|
+
mapId: data?.mapId,
|
|
445
620
|
currentServerTick: this.estimateServerTick(),
|
|
446
621
|
tickDurationMs: this.getPhysicsTickDurationMs(),
|
|
447
622
|
});
|
|
448
623
|
});
|
|
449
624
|
|
|
450
625
|
this.webSocket.on("projectile:impactBatch", (data) => {
|
|
451
|
-
this.
|
|
626
|
+
if (!this.shouldProcessProjectilePacket(data)) return;
|
|
627
|
+
this.projectiles.impactBatch(data?.impacts ?? [], {
|
|
628
|
+
mapId: data?.mapId,
|
|
629
|
+
});
|
|
452
630
|
});
|
|
453
631
|
|
|
454
632
|
this.webSocket.on("projectile:destroyBatch", (data) => {
|
|
455
|
-
this.
|
|
633
|
+
if (!this.shouldProcessProjectilePacket(data)) return;
|
|
634
|
+
this.projectiles.destroyBatch(data?.projectiles ?? [], {
|
|
635
|
+
mapId: data?.mapId,
|
|
636
|
+
});
|
|
456
637
|
});
|
|
457
638
|
|
|
458
|
-
this.webSocket.on("projectile:clear", () => {
|
|
639
|
+
this.webSocket.on("projectile:clear", (data) => {
|
|
640
|
+
if (!this.shouldProcessProjectilePacket(data)) return;
|
|
459
641
|
this.projectiles.clear();
|
|
460
642
|
});
|
|
461
643
|
|
|
@@ -573,6 +755,36 @@ export class RpgClientEngine<T = any> {
|
|
|
573
755
|
})
|
|
574
756
|
}
|
|
575
757
|
|
|
758
|
+
private beginMapTransfer(nextMapId?: string) {
|
|
759
|
+
this.mapTransitionInProgress = true;
|
|
760
|
+
this.currentMapRoomId = nextMapId;
|
|
761
|
+
this.sceneResetQueued = false;
|
|
762
|
+
this.clearClientPredictionStates();
|
|
763
|
+
this.sceneMap.weatherState.set(null);
|
|
764
|
+
this.sceneMap.lightingState.set(null);
|
|
765
|
+
this.sceneMap.clearLightSpots();
|
|
766
|
+
this.clearComponentAnimations();
|
|
767
|
+
this.projectiles.setMapId(nextMapId);
|
|
768
|
+
this.cameraFollowTargetId.set(null);
|
|
769
|
+
this.sceneMap.reset();
|
|
770
|
+
this.sceneMap.loadPhysic();
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
private clearComponentAnimations() {
|
|
774
|
+
this.componentAnimations.forEach((componentAnimation) => {
|
|
775
|
+
componentAnimation.instance?.clear?.();
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
private shouldProcessProjectilePacket(data: any): boolean {
|
|
780
|
+
if (this.mapTransitionInProgress) return false;
|
|
781
|
+
const packetMapId = normalizeRoomMapId(
|
|
782
|
+
typeof data?.mapId === "string" ? data.mapId : undefined,
|
|
783
|
+
);
|
|
784
|
+
const currentMapId = normalizeRoomMapId(this.currentMapRoomId);
|
|
785
|
+
return !packetMapId || !currentMapId || packetMapId === currentMapId;
|
|
786
|
+
}
|
|
787
|
+
|
|
576
788
|
private async callConnectError(error: any) {
|
|
577
789
|
await lastValueFrom(this.hooks.callHooks("client-engine-onConnectError", this, error, this.socket));
|
|
578
790
|
}
|
|
@@ -732,14 +944,10 @@ export class RpgClientEngine<T = any> {
|
|
|
732
944
|
query: transferToken ? { transferToken } : undefined,
|
|
733
945
|
})
|
|
734
946
|
try {
|
|
735
|
-
await this.webSocket.reconnect(
|
|
736
|
-
const saveClient = inject(SaveClientService);
|
|
737
|
-
saveClient.initialize();
|
|
738
|
-
this.initListeners()
|
|
739
|
-
this.guiService._initialize()
|
|
740
|
-
})
|
|
947
|
+
await this.webSocket.reconnect()
|
|
741
948
|
}
|
|
742
949
|
catch (error) {
|
|
950
|
+
this.mapTransitionInProgress = false;
|
|
743
951
|
this.stopPingPong();
|
|
744
952
|
await this.callConnectError(error);
|
|
745
953
|
throw error;
|
|
@@ -765,6 +973,8 @@ export class RpgClientEngine<T = any> {
|
|
|
765
973
|
|
|
766
974
|
// Signal that map loading is completed (this should be last to ensure other checks are done)
|
|
767
975
|
this.mapLoadCompleted$.next(true);
|
|
976
|
+
this.currentMapRoomId = mapId;
|
|
977
|
+
this.mapTransitionInProgress = false;
|
|
768
978
|
this.sceneMap.configureClientPrediction(this.predictionEnabled);
|
|
769
979
|
this.sceneMap.loadPhysic()
|
|
770
980
|
}
|
|
@@ -833,17 +1043,29 @@ export class RpgClientEngine<T = any> {
|
|
|
833
1043
|
|
|
834
1044
|
// If not in cache and resolver exists, use it
|
|
835
1045
|
if (this.spritesheetResolver) {
|
|
1046
|
+
if (this.spritesheetPromises.has(id)) {
|
|
1047
|
+
return this.spritesheetPromises.get(id);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
836
1050
|
const result = this.spritesheetResolver(id);
|
|
837
1051
|
|
|
838
1052
|
// Check if result is a Promise
|
|
839
1053
|
if (result instanceof Promise) {
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1054
|
+
const promise = result
|
|
1055
|
+
.then((spritesheet) => {
|
|
1056
|
+
if (spritesheet) {
|
|
1057
|
+
// Cache the resolved spritesheet
|
|
1058
|
+
this.spritesheets.set(id, spritesheet);
|
|
1059
|
+
}
|
|
1060
|
+
this.spritesheetPromises.delete(id);
|
|
1061
|
+
return spritesheet;
|
|
1062
|
+
})
|
|
1063
|
+
.catch((error) => {
|
|
1064
|
+
this.spritesheetPromises.delete(id);
|
|
1065
|
+
throw error;
|
|
1066
|
+
});
|
|
1067
|
+
this.spritesheetPromises.set(id, promise);
|
|
1068
|
+
return promise;
|
|
847
1069
|
} else {
|
|
848
1070
|
// Synchronous result
|
|
849
1071
|
if (result) {
|
|
@@ -1290,6 +1512,29 @@ export class RpgClientEngine<T = any> {
|
|
|
1290
1512
|
return this.spriteComponents.get(id);
|
|
1291
1513
|
}
|
|
1292
1514
|
|
|
1515
|
+
/**
|
|
1516
|
+
* Register a custom event component resolver.
|
|
1517
|
+
*
|
|
1518
|
+
* The last resolver returning a component wins. This lets later modules
|
|
1519
|
+
* override earlier defaults without replacing the whole map scene.
|
|
1520
|
+
*
|
|
1521
|
+
* @param resolver - Function receiving the synced event object
|
|
1522
|
+
* @returns The registered resolver
|
|
1523
|
+
*/
|
|
1524
|
+
addEventComponentResolver(resolver: EventComponentResolver) {
|
|
1525
|
+
return this.eventComponentResolvers.add(resolver);
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
/**
|
|
1529
|
+
* Resolve the custom CanvasEngine component for an event, if any.
|
|
1530
|
+
*
|
|
1531
|
+
* @param event - Synced client event object
|
|
1532
|
+
* @returns The component/config returned by the last matching resolver
|
|
1533
|
+
*/
|
|
1534
|
+
resolveEventComponent(event: RpgClientEvent): EventComponentConfig | null {
|
|
1535
|
+
return this.eventComponentResolvers.resolve(event);
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1293
1538
|
registerProjectileComponent(type: string, component: any) {
|
|
1294
1539
|
return this.projectiles.register(type, component);
|
|
1295
1540
|
}
|
|
@@ -1298,6 +1543,42 @@ export class RpgClientEngine<T = any> {
|
|
|
1298
1543
|
return this.projectiles.get(type);
|
|
1299
1544
|
}
|
|
1300
1545
|
|
|
1546
|
+
/**
|
|
1547
|
+
* Register a named client visual macro.
|
|
1548
|
+
*
|
|
1549
|
+
* Client visuals are small client-side functions that group existing visual
|
|
1550
|
+
* primitives such as flash, sound, component animations, sprite animation, or
|
|
1551
|
+
* map shake. The server sends only the visual name and a serializable payload.
|
|
1552
|
+
*
|
|
1553
|
+
* @param name - Stable visual name sent by the server
|
|
1554
|
+
* @param handler - Client-side visual handler
|
|
1555
|
+
* @returns The registered handler
|
|
1556
|
+
*/
|
|
1557
|
+
registerClientVisual(name: string, handler: ClientVisualHandler) {
|
|
1558
|
+
return this.clientVisuals.register(name, handler);
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
/**
|
|
1562
|
+
* Register several named client visual macros.
|
|
1563
|
+
*
|
|
1564
|
+
* @param visuals - Map of visual names to client-side handlers
|
|
1565
|
+
*/
|
|
1566
|
+
registerClientVisuals(visuals: ClientVisualMap) {
|
|
1567
|
+
this.clientVisuals.registerMany(visuals);
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
/**
|
|
1571
|
+
* Play a registered client visual locally.
|
|
1572
|
+
*
|
|
1573
|
+
* This is also used by the websocket listener when the server calls
|
|
1574
|
+
* `player.clientVisual()` or `map.clientVisual()`.
|
|
1575
|
+
*
|
|
1576
|
+
* @param packet - Visual name and serializable payload
|
|
1577
|
+
*/
|
|
1578
|
+
playClientVisual(packet: ClientVisualPacket) {
|
|
1579
|
+
return this.clientVisuals.play(packet, this);
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1301
1582
|
/**
|
|
1302
1583
|
* Add a component animation to the engine
|
|
1303
1584
|
*
|
|
@@ -1406,7 +1687,7 @@ export class RpgClientEngine<T = any> {
|
|
|
1406
1687
|
});
|
|
1407
1688
|
}
|
|
1408
1689
|
|
|
1409
|
-
async processInput({ input }: { input:
|
|
1690
|
+
async processInput({ input }: { input: RpgMovementInput }) {
|
|
1410
1691
|
if (this.stopProcessingInput) return;
|
|
1411
1692
|
|
|
1412
1693
|
const currentPlayer = this.sceneMap.getCurrentPlayer() as any;
|
|
@@ -1419,10 +1700,20 @@ export class RpgClientEngine<T = any> {
|
|
|
1419
1700
|
}
|
|
1420
1701
|
|
|
1421
1702
|
const timestamp = Date.now();
|
|
1703
|
+
const movementInput = isDashInput(input)
|
|
1704
|
+
? normalizeDashInput(input, currentPlayer?.direction?.())
|
|
1705
|
+
: input;
|
|
1706
|
+
if (!movementInput) return;
|
|
1707
|
+
if (isDashInput(movementInput)) {
|
|
1708
|
+
const cooldown = movementInput.cooldown ?? DEFAULT_DASH_COOLDOWN_MS;
|
|
1709
|
+
if (timestamp < this.dashLockedUntil) return;
|
|
1710
|
+
this.dashLockedUntil = timestamp + cooldown;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1422
1713
|
let frame: number;
|
|
1423
1714
|
let tick: number;
|
|
1424
1715
|
if (this.predictionEnabled && this.prediction) {
|
|
1425
|
-
const meta = this.prediction.recordInput(
|
|
1716
|
+
const meta = this.prediction.recordInput(movementInput, timestamp);
|
|
1426
1717
|
frame = meta.frame;
|
|
1427
1718
|
tick = meta.tick;
|
|
1428
1719
|
} else {
|
|
@@ -1430,12 +1721,11 @@ export class RpgClientEngine<T = any> {
|
|
|
1430
1721
|
tick = this.getPhysicsTick();
|
|
1431
1722
|
}
|
|
1432
1723
|
this.inputFrameCounter = frame;
|
|
1433
|
-
this.hooks.callHooks("client-engine-onInput", this, { input, playerId: this.playerId }).subscribe();
|
|
1724
|
+
this.hooks.callHooks("client-engine-onInput", this, { input: movementInput, playerId: this.playerId }).subscribe();
|
|
1434
1725
|
|
|
1435
1726
|
const bodyReady = this.ensureCurrentPlayerBody();
|
|
1436
1727
|
if (currentPlayer && bodyReady) {
|
|
1437
|
-
|
|
1438
|
-
(this.sceneMap as any).moveBody(currentPlayer, input);
|
|
1728
|
+
this.applyPredictedMovementInput(currentPlayer, movementInput);
|
|
1439
1729
|
if (this.predictionEnabled && this.prediction) {
|
|
1440
1730
|
this.pendingPredictionFrames.push(frame);
|
|
1441
1731
|
if (this.pendingPredictionFrames.length > 240) {
|
|
@@ -1444,8 +1734,21 @@ export class RpgClientEngine<T = any> {
|
|
|
1444
1734
|
}
|
|
1445
1735
|
}
|
|
1446
1736
|
|
|
1447
|
-
this.emitMovePacket(
|
|
1448
|
-
this.lastInputTime =
|
|
1737
|
+
this.emitMovePacket(movementInput, frame, tick, timestamp, true);
|
|
1738
|
+
this.lastInputTime = isDashInput(movementInput)
|
|
1739
|
+
? Date.now() + (movementInput.duration ?? DEFAULT_DASH_DURATION_MS)
|
|
1740
|
+
: Date.now();
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
async processDash(input: Partial<RpgDashInput> = {}) {
|
|
1744
|
+
const currentPlayer = this.sceneMap.getCurrentPlayer() as any;
|
|
1745
|
+
const fallbackDirection =
|
|
1746
|
+
typeof currentPlayer?.direction === "function"
|
|
1747
|
+
? currentPlayer.direction()
|
|
1748
|
+
: currentPlayer?.direction;
|
|
1749
|
+
const dashInput = normalizeDashInput(input, fallbackDirection);
|
|
1750
|
+
if (!dashInput) return;
|
|
1751
|
+
await this.processInput({ input: dashInput });
|
|
1449
1752
|
}
|
|
1450
1753
|
|
|
1451
1754
|
processAction(action: RpgActionName, data?: any): void;
|
|
@@ -1485,6 +1788,10 @@ export class RpgClientEngine<T = any> {
|
|
|
1485
1788
|
return this.sceneMap
|
|
1486
1789
|
}
|
|
1487
1790
|
|
|
1791
|
+
getObjectById(id: string) {
|
|
1792
|
+
return this.sceneMap?.getObjectById(id);
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1488
1795
|
private getPhysicsTick(): number {
|
|
1489
1796
|
return (this.sceneMap as any)?.getTick?.() ?? 0;
|
|
1490
1797
|
}
|
|
@@ -1617,7 +1924,7 @@ export class RpgClientEngine<T = any> {
|
|
|
1617
1924
|
input: entry.direction,
|
|
1618
1925
|
x: state.x,
|
|
1619
1926
|
y: state.y,
|
|
1620
|
-
direction: state.direction ?? entry.direction,
|
|
1927
|
+
direction: state.direction ?? resolveMoveDirection(entry.direction),
|
|
1621
1928
|
});
|
|
1622
1929
|
}
|
|
1623
1930
|
if (trajectory.length > this.MAX_MOVE_TRAJECTORY_POINTS) {
|
|
@@ -1627,7 +1934,7 @@ export class RpgClientEngine<T = any> {
|
|
|
1627
1934
|
}
|
|
1628
1935
|
|
|
1629
1936
|
private emitMovePacket(
|
|
1630
|
-
input:
|
|
1937
|
+
input: RpgMovementInput,
|
|
1631
1938
|
frame: number,
|
|
1632
1939
|
tick: number,
|
|
1633
1940
|
timestamp: number,
|
|
@@ -1682,6 +1989,22 @@ export class RpgClientEngine<T = any> {
|
|
|
1682
1989
|
this.emitMovePacket(latest.direction, latest.frame, latest.tick, now, false);
|
|
1683
1990
|
}
|
|
1684
1991
|
|
|
1992
|
+
private applyPredictedMovementInput(
|
|
1993
|
+
player: any,
|
|
1994
|
+
input: RpgMovementInput
|
|
1995
|
+
): boolean {
|
|
1996
|
+
if (isDashInput(input)) {
|
|
1997
|
+
const direction = vectorToDirection(input.direction);
|
|
1998
|
+
player.changeDirection(direction);
|
|
1999
|
+
return Boolean((this.sceneMap as any).dashBody?.(player, input));
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
const direction = resolveMoveDirection(input);
|
|
2003
|
+
if (!direction) return false;
|
|
2004
|
+
player.changeDirection(direction);
|
|
2005
|
+
return Boolean((this.sceneMap as any).moveBody?.(player, direction));
|
|
2006
|
+
}
|
|
2007
|
+
|
|
1685
2008
|
private getLocalPlayerState(): PredictionState<Direction> {
|
|
1686
2009
|
const currentPlayer = this.sceneMap?.getCurrentPlayer();
|
|
1687
2010
|
if (!currentPlayer) {
|
|
@@ -1725,7 +2048,7 @@ export class RpgClientEngine<T = any> {
|
|
|
1725
2048
|
? configuredMaxEntries
|
|
1726
2049
|
: Math.max(600, Math.ceil(historyTtlMs / 16) + 120);
|
|
1727
2050
|
this.sceneMap?.configureClientPrediction?.(true);
|
|
1728
|
-
this.prediction = new PredictionController<Direction>({
|
|
2051
|
+
this.prediction = new PredictionController<RpgMovementInput, Direction>({
|
|
1729
2052
|
correctionThreshold: (this.globalConfig as any)?.prediction?.correctionThreshold ?? this.SERVER_CORRECTION_THRESHOLD,
|
|
1730
2053
|
historyTtlMs,
|
|
1731
2054
|
maxHistoryEntries,
|
|
@@ -1940,7 +2263,7 @@ export class RpgClientEngine<T = any> {
|
|
|
1940
2263
|
|
|
1941
2264
|
private reconcilePrediction(
|
|
1942
2265
|
authoritativeState: PredictionState<Direction>,
|
|
1943
|
-
pendingInputs: PredictionHistoryEntry<Direction>[],
|
|
2266
|
+
pendingInputs: PredictionHistoryEntry<RpgMovementInput, Direction>[],
|
|
1944
2267
|
): void {
|
|
1945
2268
|
const player = this.getCurrentPlayer() as any;
|
|
1946
2269
|
if (!player) {
|
|
@@ -1962,7 +2285,7 @@ export class RpgClientEngine<T = any> {
|
|
|
1962
2285
|
const replayInputs = pendingInputs.slice(-600);
|
|
1963
2286
|
for (const entry of replayInputs) {
|
|
1964
2287
|
if (!entry?.direction) continue;
|
|
1965
|
-
|
|
2288
|
+
this.applyPredictedMovementInput(player, entry.direction);
|
|
1966
2289
|
this.sceneMap.stepPredictionTick();
|
|
1967
2290
|
this.prediction?.attachPredictedState(entry.frame, this.getLocalPlayerState());
|
|
1968
2291
|
}
|
|
@@ -2065,7 +2388,16 @@ export class RpgClientEngine<T = any> {
|
|
|
2065
2388
|
if (this.pointerMoveHandler && this.pointerCanvas) {
|
|
2066
2389
|
this.pointerCanvas.removeEventListener('pointermove', this.pointerMoveHandler);
|
|
2067
2390
|
this.pointerCanvas.removeEventListener('pointerdown', this.pointerMoveHandler);
|
|
2391
|
+
if (this.pointerUpHandler) {
|
|
2392
|
+
this.pointerCanvas.removeEventListener('pointerup', this.pointerUpHandler);
|
|
2393
|
+
}
|
|
2394
|
+
if (this.pointerCancelHandler) {
|
|
2395
|
+
this.pointerCanvas.removeEventListener('pointercancel', this.pointerCancelHandler);
|
|
2396
|
+
this.pointerCanvas.removeEventListener('pointerleave', this.pointerCancelHandler);
|
|
2397
|
+
}
|
|
2068
2398
|
this.pointerMoveHandler = undefined;
|
|
2399
|
+
this.pointerUpHandler = undefined;
|
|
2400
|
+
this.pointerCancelHandler = undefined;
|
|
2069
2401
|
this.pointerCanvas = undefined;
|
|
2070
2402
|
}
|
|
2071
2403
|
|
|
@@ -2142,6 +2474,7 @@ export class RpgClientEngine<T = any> {
|
|
|
2142
2474
|
this.cameraFollowTargetId.set(null);
|
|
2143
2475
|
this.spriteComponentsBehind.set([]);
|
|
2144
2476
|
this.spriteComponentsInFront.set([]);
|
|
2477
|
+
this.eventComponentResolvers.clear();
|
|
2145
2478
|
|
|
2146
2479
|
// Clear maps and arrays
|
|
2147
2480
|
this.spritesheets.clear();
|