@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/dist/RpgClientEngine.js
CHANGED
|
@@ -19,13 +19,72 @@ import __ce_component$3 from "./components/dynamics/bar.ce.js";
|
|
|
19
19
|
import __ce_component$4 from "./components/dynamics/shape.ce.js";
|
|
20
20
|
import __ce_component$5 from "./components/dynamics/image.ce.js";
|
|
21
21
|
import { NotificationManager } from "./Gui/NotificationManager.js";
|
|
22
|
+
import { normalizeRoomMapId } from "./utils/mapId.js";
|
|
22
23
|
import { ProjectileManager } from "./Game/ProjectileManager.js";
|
|
24
|
+
import { ClientVisualRegistry } from "./Game/ClientVisuals.js";
|
|
23
25
|
import { createClientPointerContext } from "./services/pointerContext.js";
|
|
26
|
+
import { RpgClientInteractions } from "./services/interactions.js";
|
|
27
|
+
import { EventComponentResolverRegistry } from "./Game/EventComponentResolver.js";
|
|
28
|
+
import { RpgClientBuiltinI18n } from "./i18n.js";
|
|
24
29
|
import { Howl, bootstrapCanvas, signal, trigger } from "canvasengine";
|
|
25
|
-
import { Direction, ModulesToken, PredictionController, Vector2, normalizeLightingState } from "@rpgjs/common";
|
|
30
|
+
import { Direction, ModulesToken, PredictionController, Vector2, getOrCreateI18nService, normalizeLightingState } from "@rpgjs/common";
|
|
26
31
|
import { BehaviorSubject, combineLatest, filter, lastValueFrom, switchMap, take } from "rxjs";
|
|
27
32
|
import * as PIXI from "pixi.js";
|
|
28
33
|
//#region src/RpgClientEngine.ts
|
|
34
|
+
var DEFAULT_DASH_ADDITIONAL_SPEED = 8;
|
|
35
|
+
var DEFAULT_DASH_DURATION_MS = 180;
|
|
36
|
+
var DEFAULT_DASH_COOLDOWN_MS = 450;
|
|
37
|
+
var isDashInput = (input) => typeof input === "object" && input !== null && input.type === "dash";
|
|
38
|
+
var isMoveInput = (input) => typeof input === "object" && input !== null && input.type === "move";
|
|
39
|
+
var resolveMoveDirection = (input) => {
|
|
40
|
+
if (isMoveInput(input)) return input.direction;
|
|
41
|
+
if (typeof input === "string" || typeof input === "number") return input;
|
|
42
|
+
};
|
|
43
|
+
var directionToVector = (direction) => {
|
|
44
|
+
switch (direction) {
|
|
45
|
+
case Direction.Left: return {
|
|
46
|
+
x: -1,
|
|
47
|
+
y: 0
|
|
48
|
+
};
|
|
49
|
+
case Direction.Right: return {
|
|
50
|
+
x: 1,
|
|
51
|
+
y: 0
|
|
52
|
+
};
|
|
53
|
+
case Direction.Up: return {
|
|
54
|
+
x: 0,
|
|
55
|
+
y: -1
|
|
56
|
+
};
|
|
57
|
+
case Direction.Down:
|
|
58
|
+
default: return {
|
|
59
|
+
x: 0,
|
|
60
|
+
y: 1
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var vectorToDirection = (direction) => {
|
|
65
|
+
if (Math.abs(direction.x) > Math.abs(direction.y)) return direction.x < 0 ? Direction.Left : Direction.Right;
|
|
66
|
+
return direction.y < 0 ? Direction.Up : Direction.Down;
|
|
67
|
+
};
|
|
68
|
+
var normalizeDashInput = (input, fallbackDirection) => {
|
|
69
|
+
const rawDirection = input.direction ?? directionToVector(fallbackDirection);
|
|
70
|
+
const rawX = Number(rawDirection?.x ?? 0);
|
|
71
|
+
const rawY = Number(rawDirection?.y ?? 0);
|
|
72
|
+
const magnitude = Math.hypot(rawX, rawY);
|
|
73
|
+
if (!Number.isFinite(magnitude) || magnitude <= 0) return null;
|
|
74
|
+
const additionalSpeed = typeof input.additionalSpeed === "number" && Number.isFinite(input.additionalSpeed) ? Math.max(0, Math.min(input.additionalSpeed, 64)) : DEFAULT_DASH_ADDITIONAL_SPEED;
|
|
75
|
+
const duration = typeof input.duration === "number" && Number.isFinite(input.duration) ? Math.max(1, Math.min(input.duration, 1e3)) : DEFAULT_DASH_DURATION_MS;
|
|
76
|
+
const cooldown = typeof input.cooldown === "number" && Number.isFinite(input.cooldown) ? Math.max(0, Math.min(input.cooldown, 5e3)) : DEFAULT_DASH_COOLDOWN_MS;
|
|
77
|
+
return {
|
|
78
|
+
type: "dash",
|
|
79
|
+
direction: {
|
|
80
|
+
x: rawX / magnitude,
|
|
81
|
+
y: rawY / magnitude
|
|
82
|
+
},
|
|
83
|
+
additionalSpeed,
|
|
84
|
+
duration,
|
|
85
|
+
cooldown
|
|
86
|
+
};
|
|
87
|
+
};
|
|
29
88
|
var RpgClientEngine = class {
|
|
30
89
|
constructor(context) {
|
|
31
90
|
this.context = context;
|
|
@@ -34,14 +93,18 @@ var RpgClientEngine = class {
|
|
|
34
93
|
this.width = signal("100%");
|
|
35
94
|
this.height = signal("100%");
|
|
36
95
|
this.spritesheets = /* @__PURE__ */ new Map();
|
|
96
|
+
this.spritesheetPromises = /* @__PURE__ */ new Map();
|
|
37
97
|
this.sounds = /* @__PURE__ */ new Map();
|
|
38
98
|
this.componentAnimations = [];
|
|
99
|
+
this.clientVisuals = new ClientVisualRegistry();
|
|
39
100
|
this.pointer = createClientPointerContext();
|
|
101
|
+
this.interactions = new RpgClientInteractions(this);
|
|
40
102
|
this.particleSettings = { emitters: [] };
|
|
41
103
|
this.playerIdSignal = signal(null);
|
|
42
104
|
this.spriteComponentsBehind = signal([]);
|
|
43
105
|
this.spriteComponentsInFront = signal([]);
|
|
44
106
|
this.spriteComponents = /* @__PURE__ */ new Map();
|
|
107
|
+
this.eventComponentResolvers = new EventComponentResolverRegistry();
|
|
45
108
|
this.cameraFollowTargetId = signal(null);
|
|
46
109
|
this.mapShakeTrigger = trigger();
|
|
47
110
|
this.controlsReady = signal(void 0);
|
|
@@ -53,6 +116,7 @@ var RpgClientEngine = class {
|
|
|
53
116
|
this.lastClientPhysicsStepAt = 0;
|
|
54
117
|
this.frameOffset = 0;
|
|
55
118
|
this.latestServerTickAt = 0;
|
|
119
|
+
this.dashLockedUntil = 0;
|
|
56
120
|
this.rtt = 0;
|
|
57
121
|
this.pingInterval = null;
|
|
58
122
|
this.PING_INTERVAL_MS = 5e3;
|
|
@@ -66,6 +130,8 @@ var RpgClientEngine = class {
|
|
|
66
130
|
this.playersReceived$ = new BehaviorSubject(false);
|
|
67
131
|
this.eventsReceived$ = new BehaviorSubject(false);
|
|
68
132
|
this.sceneResetQueued = false;
|
|
133
|
+
this.mapTransitionInProgress = false;
|
|
134
|
+
this.socketListenersInitialized = false;
|
|
69
135
|
this.tickSubscriptions = [];
|
|
70
136
|
this.pendingSyncPackets = [];
|
|
71
137
|
this.notificationManager = new NotificationManager();
|
|
@@ -73,6 +139,8 @@ var RpgClientEngine = class {
|
|
|
73
139
|
this.guiService = inject(RpgGui);
|
|
74
140
|
this.loadMapService = inject(LoadMapToken);
|
|
75
141
|
this.hooks = inject(ModulesToken);
|
|
142
|
+
this.i18nService = getOrCreateI18nService(context);
|
|
143
|
+
this.i18nService.addMessages(RpgClientBuiltinI18n, "rpgjs-client", 0);
|
|
76
144
|
this.projectiles = new ProjectileManager(this.hooks, (projectile) => this.predictProjectileImpact(projectile));
|
|
77
145
|
this.globalConfig = inject(GlobalConfigToken);
|
|
78
146
|
if (!this.globalConfig) this.globalConfig = {};
|
|
@@ -96,6 +164,21 @@ var RpgClientEngine = class {
|
|
|
96
164
|
this.predictionEnabled = this.globalConfig?.prediction?.enabled !== false;
|
|
97
165
|
this.initializePredictionController();
|
|
98
166
|
}
|
|
167
|
+
setLocale(locale) {
|
|
168
|
+
this.locale = locale;
|
|
169
|
+
}
|
|
170
|
+
getLocale() {
|
|
171
|
+
return this.locale || this.i18nService.defaultLocale;
|
|
172
|
+
}
|
|
173
|
+
t(key, params) {
|
|
174
|
+
return this.i18nService.t(key, params, this.getLocale());
|
|
175
|
+
}
|
|
176
|
+
i18n() {
|
|
177
|
+
return {
|
|
178
|
+
locale: this.getLocale(),
|
|
179
|
+
t: (key, params) => this.t(key, params)
|
|
180
|
+
};
|
|
181
|
+
}
|
|
99
182
|
/**
|
|
100
183
|
* Assigns a CanvasEngine KeyboardControls instance to the dependency injection context
|
|
101
184
|
*
|
|
@@ -160,7 +243,6 @@ var RpgClientEngine = class {
|
|
|
160
243
|
this.renderer = app.renderer;
|
|
161
244
|
this.setupPointerTracking();
|
|
162
245
|
this.tick = canvasElement?.propObservables?.context["tick"].observable;
|
|
163
|
-
this.flushPendingSyncPackets();
|
|
164
246
|
const inputCheckSubscription = this.tick.subscribe(() => {
|
|
165
247
|
if (Date.now() - this.lastInputTime > 100) {
|
|
166
248
|
const player = this.getCurrentPlayer();
|
|
@@ -171,6 +253,7 @@ var RpgClientEngine = class {
|
|
|
171
253
|
this.tickSubscriptions.push(inputCheckSubscription);
|
|
172
254
|
this.hooks.callHooks("client-spritesheets-load", this).subscribe();
|
|
173
255
|
this.hooks.callHooks("client-spritesheetResolver-load", this).subscribe();
|
|
256
|
+
this.flushPendingSyncPackets();
|
|
174
257
|
this.hooks.callHooks("client-sounds-load", this).subscribe();
|
|
175
258
|
this.hooks.callHooks("client-soundResolver-load", this).subscribe();
|
|
176
259
|
RpgSound.init(this);
|
|
@@ -178,7 +261,9 @@ var RpgClientEngine = class {
|
|
|
178
261
|
this.hooks.callHooks("client-gui-load", this).subscribe();
|
|
179
262
|
this.hooks.callHooks("client-particles-load", this).subscribe();
|
|
180
263
|
this.hooks.callHooks("client-componentAnimations-load", this).subscribe();
|
|
264
|
+
this.hooks.callHooks("client-clientVisuals-load", this).subscribe();
|
|
181
265
|
this.hooks.callHooks("client-projectiles-load", this).subscribe();
|
|
266
|
+
this.hooks.callHooks("client-interactions-load", this).subscribe();
|
|
182
267
|
this.hooks.callHooks("client-sprite-load", this).subscribe();
|
|
183
268
|
await lastValueFrom(this.hooks.callHooks("client-engine-onStart", this));
|
|
184
269
|
this.resizeHandler = () => {
|
|
@@ -210,7 +295,7 @@ var RpgClientEngine = class {
|
|
|
210
295
|
const canvas = renderer?.canvas ?? renderer?.view ?? this.canvasApp?.canvas;
|
|
211
296
|
if (!canvas || typeof canvas.addEventListener !== "function") return;
|
|
212
297
|
this.pointerCanvas = canvas;
|
|
213
|
-
|
|
298
|
+
const updatePointer = (event) => {
|
|
214
299
|
const rect = canvas.getBoundingClientRect();
|
|
215
300
|
const screen = {
|
|
216
301
|
x: event.clientX - rect.left,
|
|
@@ -233,11 +318,59 @@ var RpgClientEngine = class {
|
|
|
233
318
|
}
|
|
234
319
|
this.pointer.update(screen, world);
|
|
235
320
|
};
|
|
321
|
+
this.pointerMoveHandler = (event) => {
|
|
322
|
+
updatePointer(event);
|
|
323
|
+
this.interactions.handlePointerMove(event);
|
|
324
|
+
};
|
|
325
|
+
this.pointerUpHandler = (event) => {
|
|
326
|
+
updatePointer(event);
|
|
327
|
+
this.interactions.handlePointerUp(event);
|
|
328
|
+
};
|
|
329
|
+
this.pointerCancelHandler = (event) => {
|
|
330
|
+
updatePointer(event);
|
|
331
|
+
this.interactions.cancelDrag(event);
|
|
332
|
+
};
|
|
236
333
|
canvas.addEventListener("pointermove", this.pointerMoveHandler);
|
|
237
334
|
canvas.addEventListener("pointerdown", this.pointerMoveHandler);
|
|
335
|
+
canvas.addEventListener("pointerup", this.pointerUpHandler);
|
|
336
|
+
canvas.addEventListener("pointercancel", this.pointerCancelHandler);
|
|
337
|
+
canvas.addEventListener("pointerleave", this.pointerCancelHandler);
|
|
338
|
+
}
|
|
339
|
+
updatePointerFromInteractionEvent(event) {
|
|
340
|
+
const global = event?.global ?? event?.data?.global;
|
|
341
|
+
if (!global) {
|
|
342
|
+
this.pointer.updateFromEvent(event);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const screen = {
|
|
346
|
+
x: Number(global.x),
|
|
347
|
+
y: Number(global.y)
|
|
348
|
+
};
|
|
349
|
+
if (!Number.isFinite(screen.x) || !Number.isFinite(screen.y)) {
|
|
350
|
+
this.pointer.updateFromEvent(event);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const viewport = this.findViewportInstance();
|
|
354
|
+
if (viewport && typeof viewport.toWorld === "function") {
|
|
355
|
+
const point = viewport.toWorld(screen.x, screen.y);
|
|
356
|
+
this.pointer.update(screen, {
|
|
357
|
+
x: Number(point.x),
|
|
358
|
+
y: Number(point.y)
|
|
359
|
+
});
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
this.pointer.update(screen);
|
|
238
363
|
}
|
|
239
364
|
findViewportInstance() {
|
|
240
|
-
|
|
365
|
+
const find = (node) => {
|
|
366
|
+
if (!node) return void 0;
|
|
367
|
+
if (typeof node?.toWorld === "function" || node?.constructor?.name === "Viewport") return node;
|
|
368
|
+
for (const child of node.children ?? []) {
|
|
369
|
+
const viewport = find(child);
|
|
370
|
+
if (viewport) return viewport;
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
return find(this.canvasApp?.stage);
|
|
241
374
|
}
|
|
242
375
|
prepareSyncPayload(data) {
|
|
243
376
|
const payload = { ...data ?? {} };
|
|
@@ -271,6 +404,8 @@ var RpgClientEngine = class {
|
|
|
271
404
|
};
|
|
272
405
|
}
|
|
273
406
|
initListeners() {
|
|
407
|
+
if (this.socketListenersInitialized) return;
|
|
408
|
+
this.socketListenersInitialized = true;
|
|
274
409
|
this.webSocket.on("sync", (data) => {
|
|
275
410
|
if (!this.tick) {
|
|
276
411
|
this.pendingSyncPackets.push(data);
|
|
@@ -288,12 +423,8 @@ var RpgClientEngine = class {
|
|
|
288
423
|
console.debug(`[Ping/Pong] RTT: ${this.rtt}ms, ServerTick: ${data.serverTick}, FrameOffset: ${this.frameOffset}`);
|
|
289
424
|
});
|
|
290
425
|
this.webSocket.on("changeMap", (data) => {
|
|
291
|
-
|
|
292
|
-
this.
|
|
293
|
-
this.sceneMap.lightingState.set(null);
|
|
294
|
-
this.sceneMap.clearLightSpots();
|
|
295
|
-
this.projectiles.clear();
|
|
296
|
-
this.cameraFollowTargetId.set(null);
|
|
426
|
+
const nextMapId = typeof data?.mapId === "string" ? data.mapId : void 0;
|
|
427
|
+
this.beginMapTransfer(nextMapId);
|
|
297
428
|
const transferToken = typeof data?.transferToken === "string" ? data.transferToken : void 0;
|
|
298
429
|
this.loadScene(data.mapId, transferToken);
|
|
299
430
|
});
|
|
@@ -303,19 +434,27 @@ var RpgClientEngine = class {
|
|
|
303
434
|
const player = object ? this.sceneMap.getObjectById(object) : void 0;
|
|
304
435
|
this.getComponentAnimation(id).displayEffect(params, player || position);
|
|
305
436
|
});
|
|
437
|
+
this.webSocket.on("clientVisual", (data) => {
|
|
438
|
+
this.playClientVisual(data);
|
|
439
|
+
});
|
|
306
440
|
this.webSocket.on("projectile:spawnBatch", (data) => {
|
|
441
|
+
if (!this.shouldProcessProjectilePacket(data)) return;
|
|
307
442
|
this.projectiles.spawnBatch(data?.projectiles ?? [], {
|
|
443
|
+
mapId: data?.mapId,
|
|
308
444
|
currentServerTick: this.estimateServerTick(),
|
|
309
445
|
tickDurationMs: this.getPhysicsTickDurationMs()
|
|
310
446
|
});
|
|
311
447
|
});
|
|
312
448
|
this.webSocket.on("projectile:impactBatch", (data) => {
|
|
313
|
-
this.
|
|
449
|
+
if (!this.shouldProcessProjectilePacket(data)) return;
|
|
450
|
+
this.projectiles.impactBatch(data?.impacts ?? [], { mapId: data?.mapId });
|
|
314
451
|
});
|
|
315
452
|
this.webSocket.on("projectile:destroyBatch", (data) => {
|
|
316
|
-
this.
|
|
453
|
+
if (!this.shouldProcessProjectilePacket(data)) return;
|
|
454
|
+
this.projectiles.destroyBatch(data?.projectiles ?? [], { mapId: data?.mapId });
|
|
317
455
|
});
|
|
318
|
-
this.webSocket.on("projectile:clear", () => {
|
|
456
|
+
this.webSocket.on("projectile:clear", (data) => {
|
|
457
|
+
if (!this.shouldProcessProjectilePacket(data)) return;
|
|
319
458
|
this.projectiles.clear();
|
|
320
459
|
});
|
|
321
460
|
this.webSocket.on("notification", (data) => {
|
|
@@ -408,6 +547,31 @@ var RpgClientEngine = class {
|
|
|
408
547
|
this.callConnectError(error);
|
|
409
548
|
});
|
|
410
549
|
}
|
|
550
|
+
beginMapTransfer(nextMapId) {
|
|
551
|
+
this.mapTransitionInProgress = true;
|
|
552
|
+
this.currentMapRoomId = nextMapId;
|
|
553
|
+
this.sceneResetQueued = false;
|
|
554
|
+
this.clearClientPredictionStates();
|
|
555
|
+
this.sceneMap.weatherState.set(null);
|
|
556
|
+
this.sceneMap.lightingState.set(null);
|
|
557
|
+
this.sceneMap.clearLightSpots();
|
|
558
|
+
this.clearComponentAnimations();
|
|
559
|
+
this.projectiles.setMapId(nextMapId);
|
|
560
|
+
this.cameraFollowTargetId.set(null);
|
|
561
|
+
this.sceneMap.reset();
|
|
562
|
+
this.sceneMap.loadPhysic();
|
|
563
|
+
}
|
|
564
|
+
clearComponentAnimations() {
|
|
565
|
+
this.componentAnimations.forEach((componentAnimation) => {
|
|
566
|
+
componentAnimation.instance?.clear?.();
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
shouldProcessProjectilePacket(data) {
|
|
570
|
+
if (this.mapTransitionInProgress) return false;
|
|
571
|
+
const packetMapId = normalizeRoomMapId(typeof data?.mapId === "string" ? data.mapId : void 0);
|
|
572
|
+
const currentMapId = normalizeRoomMapId(this.currentMapRoomId);
|
|
573
|
+
return !packetMapId || !currentMapId || packetMapId === currentMapId;
|
|
574
|
+
}
|
|
411
575
|
async callConnectError(error) {
|
|
412
576
|
await lastValueFrom(this.hooks.callHooks("client-engine-onConnectError", this, error, this.socket));
|
|
413
577
|
}
|
|
@@ -522,12 +686,9 @@ var RpgClientEngine = class {
|
|
|
522
686
|
query: transferToken ? { transferToken } : void 0
|
|
523
687
|
});
|
|
524
688
|
try {
|
|
525
|
-
await this.webSocket.reconnect(
|
|
526
|
-
inject(SaveClientService).initialize();
|
|
527
|
-
this.initListeners();
|
|
528
|
-
this.guiService._initialize();
|
|
529
|
-
});
|
|
689
|
+
await this.webSocket.reconnect();
|
|
530
690
|
} catch (error) {
|
|
691
|
+
this.mapTransitionInProgress = false;
|
|
531
692
|
this.stopPingPong();
|
|
532
693
|
await this.callConnectError(error);
|
|
533
694
|
throw error;
|
|
@@ -539,6 +700,8 @@ var RpgClientEngine = class {
|
|
|
539
700
|
if (players && Object.keys(players).length > 0) this.playersReceived$.next(true);
|
|
540
701
|
if (this.sceneMap.events() !== void 0) this.eventsReceived$.next(true);
|
|
541
702
|
this.mapLoadCompleted$.next(true);
|
|
703
|
+
this.currentMapRoomId = mapId;
|
|
704
|
+
this.mapTransitionInProgress = false;
|
|
542
705
|
this.sceneMap.configureClientPrediction(this.predictionEnabled);
|
|
543
706
|
this.sceneMap.loadPhysic();
|
|
544
707
|
}
|
|
@@ -599,12 +762,20 @@ var RpgClientEngine = class {
|
|
|
599
762
|
getSpriteSheet(id) {
|
|
600
763
|
if (this.spritesheets.has(id)) return this.spritesheets.get(id);
|
|
601
764
|
if (this.spritesheetResolver) {
|
|
765
|
+
if (this.spritesheetPromises.has(id)) return this.spritesheetPromises.get(id);
|
|
602
766
|
const result = this.spritesheetResolver(id);
|
|
603
|
-
if (result instanceof Promise)
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
767
|
+
if (result instanceof Promise) {
|
|
768
|
+
const promise = result.then((spritesheet) => {
|
|
769
|
+
if (spritesheet) this.spritesheets.set(id, spritesheet);
|
|
770
|
+
this.spritesheetPromises.delete(id);
|
|
771
|
+
return spritesheet;
|
|
772
|
+
}).catch((error) => {
|
|
773
|
+
this.spritesheetPromises.delete(id);
|
|
774
|
+
throw error;
|
|
775
|
+
});
|
|
776
|
+
this.spritesheetPromises.set(id, promise);
|
|
777
|
+
return promise;
|
|
778
|
+
} else {
|
|
608
779
|
if (result) this.spritesheets.set(id, result);
|
|
609
780
|
return result;
|
|
610
781
|
}
|
|
@@ -960,6 +1131,27 @@ var RpgClientEngine = class {
|
|
|
960
1131
|
getSpriteComponent(id) {
|
|
961
1132
|
return this.spriteComponents.get(id);
|
|
962
1133
|
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Register a custom event component resolver.
|
|
1136
|
+
*
|
|
1137
|
+
* The last resolver returning a component wins. This lets later modules
|
|
1138
|
+
* override earlier defaults without replacing the whole map scene.
|
|
1139
|
+
*
|
|
1140
|
+
* @param resolver - Function receiving the synced event object
|
|
1141
|
+
* @returns The registered resolver
|
|
1142
|
+
*/
|
|
1143
|
+
addEventComponentResolver(resolver) {
|
|
1144
|
+
return this.eventComponentResolvers.add(resolver);
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Resolve the custom CanvasEngine component for an event, if any.
|
|
1148
|
+
*
|
|
1149
|
+
* @param event - Synced client event object
|
|
1150
|
+
* @returns The component/config returned by the last matching resolver
|
|
1151
|
+
*/
|
|
1152
|
+
resolveEventComponent(event) {
|
|
1153
|
+
return this.eventComponentResolvers.resolve(event);
|
|
1154
|
+
}
|
|
963
1155
|
registerProjectileComponent(type, component) {
|
|
964
1156
|
return this.projectiles.register(type, component);
|
|
965
1157
|
}
|
|
@@ -967,6 +1159,39 @@ var RpgClientEngine = class {
|
|
|
967
1159
|
return this.projectiles.get(type);
|
|
968
1160
|
}
|
|
969
1161
|
/**
|
|
1162
|
+
* Register a named client visual macro.
|
|
1163
|
+
*
|
|
1164
|
+
* Client visuals are small client-side functions that group existing visual
|
|
1165
|
+
* primitives such as flash, sound, component animations, sprite animation, or
|
|
1166
|
+
* map shake. The server sends only the visual name and a serializable payload.
|
|
1167
|
+
*
|
|
1168
|
+
* @param name - Stable visual name sent by the server
|
|
1169
|
+
* @param handler - Client-side visual handler
|
|
1170
|
+
* @returns The registered handler
|
|
1171
|
+
*/
|
|
1172
|
+
registerClientVisual(name, handler) {
|
|
1173
|
+
return this.clientVisuals.register(name, handler);
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Register several named client visual macros.
|
|
1177
|
+
*
|
|
1178
|
+
* @param visuals - Map of visual names to client-side handlers
|
|
1179
|
+
*/
|
|
1180
|
+
registerClientVisuals(visuals) {
|
|
1181
|
+
this.clientVisuals.registerMany(visuals);
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Play a registered client visual locally.
|
|
1185
|
+
*
|
|
1186
|
+
* This is also used by the websocket listener when the server calls
|
|
1187
|
+
* `player.clientVisual()` or `map.clientVisual()`.
|
|
1188
|
+
*
|
|
1189
|
+
* @param packet - Visual name and serializable payload
|
|
1190
|
+
*/
|
|
1191
|
+
playClientVisual(packet) {
|
|
1192
|
+
return this.clientVisuals.play(packet, this);
|
|
1193
|
+
}
|
|
1194
|
+
/**
|
|
970
1195
|
* Add a component animation to the engine
|
|
971
1196
|
*
|
|
972
1197
|
* Component animations are temporary visual effects that can be displayed
|
|
@@ -1071,10 +1296,17 @@ var RpgClientEngine = class {
|
|
|
1071
1296
|
return;
|
|
1072
1297
|
}
|
|
1073
1298
|
const timestamp = Date.now();
|
|
1299
|
+
const movementInput = isDashInput(input) ? normalizeDashInput(input, currentPlayer?.direction?.()) : input;
|
|
1300
|
+
if (!movementInput) return;
|
|
1301
|
+
if (isDashInput(movementInput)) {
|
|
1302
|
+
const cooldown = movementInput.cooldown ?? DEFAULT_DASH_COOLDOWN_MS;
|
|
1303
|
+
if (timestamp < this.dashLockedUntil) return;
|
|
1304
|
+
this.dashLockedUntil = timestamp + cooldown;
|
|
1305
|
+
}
|
|
1074
1306
|
let frame;
|
|
1075
1307
|
let tick;
|
|
1076
1308
|
if (this.predictionEnabled && this.prediction) {
|
|
1077
|
-
const meta = this.prediction.recordInput(
|
|
1309
|
+
const meta = this.prediction.recordInput(movementInput, timestamp);
|
|
1078
1310
|
frame = meta.frame;
|
|
1079
1311
|
tick = meta.tick;
|
|
1080
1312
|
} else {
|
|
@@ -1083,20 +1315,25 @@ var RpgClientEngine = class {
|
|
|
1083
1315
|
}
|
|
1084
1316
|
this.inputFrameCounter = frame;
|
|
1085
1317
|
this.hooks.callHooks("client-engine-onInput", this, {
|
|
1086
|
-
input,
|
|
1318
|
+
input: movementInput,
|
|
1087
1319
|
playerId: this.playerId
|
|
1088
1320
|
}).subscribe();
|
|
1089
1321
|
const bodyReady = this.ensureCurrentPlayerBody();
|
|
1090
1322
|
if (currentPlayer && bodyReady) {
|
|
1091
|
-
|
|
1092
|
-
this.sceneMap.moveBody(currentPlayer, input);
|
|
1323
|
+
this.applyPredictedMovementInput(currentPlayer, movementInput);
|
|
1093
1324
|
if (this.predictionEnabled && this.prediction) {
|
|
1094
1325
|
this.pendingPredictionFrames.push(frame);
|
|
1095
1326
|
if (this.pendingPredictionFrames.length > 240) this.pendingPredictionFrames = this.pendingPredictionFrames.slice(-240);
|
|
1096
1327
|
}
|
|
1097
1328
|
}
|
|
1098
|
-
this.emitMovePacket(
|
|
1099
|
-
this.lastInputTime = Date.now();
|
|
1329
|
+
this.emitMovePacket(movementInput, frame, tick, timestamp, true);
|
|
1330
|
+
this.lastInputTime = isDashInput(movementInput) ? Date.now() + (movementInput.duration ?? DEFAULT_DASH_DURATION_MS) : Date.now();
|
|
1331
|
+
}
|
|
1332
|
+
async processDash(input = {}) {
|
|
1333
|
+
const currentPlayer = this.sceneMap.getCurrentPlayer();
|
|
1334
|
+
const dashInput = normalizeDashInput(input, typeof currentPlayer?.direction === "function" ? currentPlayer.direction() : currentPlayer?.direction);
|
|
1335
|
+
if (!dashInput) return;
|
|
1336
|
+
await this.processInput({ input: dashInput });
|
|
1100
1337
|
}
|
|
1101
1338
|
processAction(action, data) {
|
|
1102
1339
|
if (this.stopProcessingInput) return;
|
|
@@ -1123,6 +1360,9 @@ var RpgClientEngine = class {
|
|
|
1123
1360
|
get scene() {
|
|
1124
1361
|
return this.sceneMap;
|
|
1125
1362
|
}
|
|
1363
|
+
getObjectById(id) {
|
|
1364
|
+
return this.sceneMap?.getObjectById(id);
|
|
1365
|
+
}
|
|
1126
1366
|
getPhysicsTick() {
|
|
1127
1367
|
return this.sceneMap?.getTick?.() ?? 0;
|
|
1128
1368
|
}
|
|
@@ -1202,7 +1442,7 @@ var RpgClientEngine = class {
|
|
|
1202
1442
|
input: entry.direction,
|
|
1203
1443
|
x: state.x,
|
|
1204
1444
|
y: state.y,
|
|
1205
|
-
direction: state.direction ?? entry.direction
|
|
1445
|
+
direction: state.direction ?? resolveMoveDirection(entry.direction)
|
|
1206
1446
|
});
|
|
1207
1447
|
}
|
|
1208
1448
|
if (trajectory.length > this.MAX_MOVE_TRAJECTORY_POINTS) return trajectory.slice(-this.MAX_MOVE_TRAJECTORY_POINTS);
|
|
@@ -1237,6 +1477,17 @@ var RpgClientEngine = class {
|
|
|
1237
1477
|
if (now - this.lastMovePathSentAt < this.MOVE_PATH_RESEND_INTERVAL_MS) return;
|
|
1238
1478
|
this.emitMovePacket(latest.direction, latest.frame, latest.tick, now, false);
|
|
1239
1479
|
}
|
|
1480
|
+
applyPredictedMovementInput(player, input) {
|
|
1481
|
+
if (isDashInput(input)) {
|
|
1482
|
+
const direction = vectorToDirection(input.direction);
|
|
1483
|
+
player.changeDirection(direction);
|
|
1484
|
+
return Boolean(this.sceneMap.dashBody?.(player, input));
|
|
1485
|
+
}
|
|
1486
|
+
const direction = resolveMoveDirection(input);
|
|
1487
|
+
if (!direction) return false;
|
|
1488
|
+
player.changeDirection(direction);
|
|
1489
|
+
return Boolean(this.sceneMap.moveBody?.(player, direction));
|
|
1490
|
+
}
|
|
1240
1491
|
getLocalPlayerState() {
|
|
1241
1492
|
const currentPlayer = this.sceneMap?.getCurrentPlayer();
|
|
1242
1493
|
if (!currentPlayer) return {
|
|
@@ -1462,7 +1713,7 @@ var RpgClientEngine = class {
|
|
|
1462
1713
|
const replayInputs = pendingInputs.slice(-600);
|
|
1463
1714
|
for (const entry of replayInputs) {
|
|
1464
1715
|
if (!entry?.direction) continue;
|
|
1465
|
-
this.
|
|
1716
|
+
this.applyPredictedMovementInput(player, entry.direction);
|
|
1466
1717
|
this.sceneMap.stepPredictionTick();
|
|
1467
1718
|
this.prediction?.attachPredictedState(entry.frame, this.getLocalPlayerState());
|
|
1468
1719
|
}
|
|
@@ -1531,7 +1782,14 @@ var RpgClientEngine = class {
|
|
|
1531
1782
|
if (this.pointerMoveHandler && this.pointerCanvas) {
|
|
1532
1783
|
this.pointerCanvas.removeEventListener("pointermove", this.pointerMoveHandler);
|
|
1533
1784
|
this.pointerCanvas.removeEventListener("pointerdown", this.pointerMoveHandler);
|
|
1785
|
+
if (this.pointerUpHandler) this.pointerCanvas.removeEventListener("pointerup", this.pointerUpHandler);
|
|
1786
|
+
if (this.pointerCancelHandler) {
|
|
1787
|
+
this.pointerCanvas.removeEventListener("pointercancel", this.pointerCancelHandler);
|
|
1788
|
+
this.pointerCanvas.removeEventListener("pointerleave", this.pointerCancelHandler);
|
|
1789
|
+
}
|
|
1534
1790
|
this.pointerMoveHandler = void 0;
|
|
1791
|
+
this.pointerUpHandler = void 0;
|
|
1792
|
+
this.pointerCancelHandler = void 0;
|
|
1535
1793
|
this.pointerCanvas = void 0;
|
|
1536
1794
|
}
|
|
1537
1795
|
const rendererStillExists = this.renderer && typeof this.renderer.destroy === "function";
|
|
@@ -1565,6 +1823,7 @@ var RpgClientEngine = class {
|
|
|
1565
1823
|
this.cameraFollowTargetId.set(null);
|
|
1566
1824
|
this.spriteComponentsBehind.set([]);
|
|
1567
1825
|
this.spriteComponentsInFront.set([]);
|
|
1826
|
+
this.eventComponentResolvers.clear();
|
|
1568
1827
|
this.spritesheets.clear();
|
|
1569
1828
|
this.sounds.clear();
|
|
1570
1829
|
this.componentAnimations = [];
|