@rpgjs/client 5.0.0-beta.10 → 5.0.0-beta.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -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.js +2 -2
- package/dist/Game/Object.js.map +1 -1
- package/dist/Game/Object.spec.d.ts +1 -0
- package/dist/Game/ProjectileManager.d.ts +98 -0
- package/dist/Game/ProjectileManager.js +196 -0
- package/dist/Game/ProjectileManager.js.map +1 -0
- package/dist/Game/ProjectileManager.spec.d.ts +1 -0
- package/dist/RpgClient.d.ts +117 -13
- package/dist/RpgClientEngine.d.ts +82 -4
- package/dist/RpgClientEngine.js +296 -51
- 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/hit.ce.js.map +1 -1
- 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 +140 -40
- package/dist/components/character.ce.js.map +1 -1
- package/dist/components/dynamics/bar.ce.js +4 -3
- package/dist/components/dynamics/bar.ce.js.map +1 -1
- package/dist/components/dynamics/image.ce.js +2 -1
- package/dist/components/dynamics/image.ce.js.map +1 -1
- package/dist/components/dynamics/shape.ce.js +3 -2
- package/dist/components/dynamics/shape.ce.js.map +1 -1
- package/dist/components/dynamics/text.ce.js +9 -8
- package/dist/components/dynamics/text.ce.js.map +1 -1
- package/dist/components/gui/dialogbox/index.ce.js +3 -2
- package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
- package/dist/components/gui/gameover.ce.js +3 -2
- package/dist/components/gui/gameover.ce.js.map +1 -1
- package/dist/components/gui/hud/hud.ce.js.map +1 -1
- package/dist/components/gui/menu/equip-menu.ce.js +2 -1
- package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/exit-menu.ce.js +2 -1
- package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/items-menu.ce.js +3 -2
- package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/main-menu.ce.js +3 -2
- package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
- package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
- package/dist/components/gui/notification/notification.ce.js.map +1 -1
- package/dist/components/gui/save-load.ce.js +2 -1
- package/dist/components/gui/save-load.ce.js.map +1 -1
- package/dist/components/gui/shop/shop.ce.js +3 -2
- package/dist/components/gui/shop/shop.ce.js.map +1 -1
- package/dist/components/gui/title-screen.ce.js +3 -2
- 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.ce.js +11 -10
- package/dist/components/player-components.ce.js.map +1 -1
- package/dist/components/prebuilt/hp-bar.ce.js +4 -3
- package/dist/components/prebuilt/hp-bar.ce.js.map +1 -1
- package/dist/components/prebuilt/light-halo.ce.js +2 -1
- package/dist/components/prebuilt/light-halo.ce.js.map +1 -1
- package/dist/components/scenes/canvas.ce.js +12 -4
- package/dist/components/scenes/canvas.ce.js.map +1 -1
- package/dist/components/scenes/draw-map.ce.js +6 -3
- package/dist/components/scenes/draw-map.ce.js.map +1 -1
- package/dist/components/scenes/event-layer.ce.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +10 -5
- package/dist/module.js +18 -0
- package/dist/module.js.map +1 -1
- package/dist/services/actionInput.d.ts +14 -0
- package/dist/services/actionInput.js +59 -0
- package/dist/services/actionInput.js.map +1 -0
- package/dist/services/actionInput.spec.d.ts +1 -0
- 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 +48 -30
- 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/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.d.ts +3 -1
- package/dist/services/standalone.js +34 -15
- package/dist/services/standalone.js.map +1 -1
- package/dist/services/standalone.spec.d.ts +1 -0
- 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 +7 -7
- 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 +46 -0
- package/src/Game/Object.ts +2 -2
- package/src/Game/ProjectileManager.spec.ts +449 -0
- package/src/Game/ProjectileManager.ts +346 -0
- package/src/RpgClient.ts +130 -15
- package/src/RpgClientEngine.ts +405 -69
- package/src/components/animations/fx.ce +101 -0
- package/src/components/animations/index.ts +4 -2
- package/src/components/character.ce +185 -40
- package/src/components/dynamics/bar.ce +4 -3
- package/src/components/dynamics/image.ce +2 -1
- package/src/components/dynamics/shape.ce +3 -2
- package/src/components/dynamics/text.ce +9 -8
- package/src/components/gui/dialogbox/index.ce +3 -2
- 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.ce +11 -10
- package/src/components/prebuilt/hp-bar.ce +4 -3
- package/src/components/prebuilt/light-halo.ce +2 -2
- package/src/components/scenes/canvas.ce +10 -2
- package/src/components/scenes/draw-map.ce +17 -3
- package/src/index.ts +4 -0
- package/src/module.ts +24 -0
- package/src/services/actionInput.spec.ts +155 -0
- package/src/services/actionInput.ts +120 -0
- package/src/services/mmorpg-connection.spec.ts +99 -0
- package/src/services/mmorpg-connection.ts +69 -0
- package/src/services/mmorpg.ts +60 -34
- package/src/services/pointerContext.spec.ts +36 -0
- package/src/services/pointerContext.ts +84 -0
- package/src/services/standalone-message.ts +7 -0
- package/src/services/standalone.spec.ts +34 -0
- package/src/services/standalone.ts +42 -12
- package/src/utils/mapId.ts +2 -0
package/dist/RpgClientEngine.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { inject } from "./core/inject.js";
|
|
2
2
|
import { WebSocketToken } from "./services/AbstractSocket.js";
|
|
3
|
+
import { normalizeActionInput } from "./services/actionInput.js";
|
|
3
4
|
import { getCanMoveValue } from "./utils/readPropValue.js";
|
|
4
5
|
import { SaveClientService } from "./services/save.js";
|
|
5
6
|
import { RpgGui } from "./Gui/Gui.js";
|
|
6
7
|
import __ce_component from "./components/scenes/canvas.ce.js";
|
|
8
|
+
import __ce_component$1 from "./components/scenes/draw-map.ce.js";
|
|
7
9
|
import { LoadMapToken } from "./services/loadMap.js";
|
|
8
10
|
import { RpgSound } from "./Sound.js";
|
|
9
11
|
import { RpgResource } from "./Resource.js";
|
|
@@ -12,30 +14,39 @@ import { RpgClientMap } from "./Game/Map.js";
|
|
|
12
14
|
import { AnimationManager } from "./Game/AnimationManager.js";
|
|
13
15
|
import { GlobalConfigToken } from "./module.js";
|
|
14
16
|
import { PrebuiltComponentAnimations } from "./components/animations/index.js";
|
|
15
|
-
import __ce_component$
|
|
16
|
-
import __ce_component$
|
|
17
|
-
import __ce_component$
|
|
18
|
-
import __ce_component$
|
|
17
|
+
import __ce_component$2 from "./components/dynamics/text.ce.js";
|
|
18
|
+
import __ce_component$3 from "./components/dynamics/bar.ce.js";
|
|
19
|
+
import __ce_component$4 from "./components/dynamics/shape.ce.js";
|
|
20
|
+
import __ce_component$5 from "./components/dynamics/image.ce.js";
|
|
19
21
|
import { NotificationManager } from "./Gui/NotificationManager.js";
|
|
22
|
+
import { normalizeRoomMapId } from "./utils/mapId.js";
|
|
23
|
+
import { ProjectileManager } from "./Game/ProjectileManager.js";
|
|
24
|
+
import { ClientVisualRegistry } from "./Game/ClientVisuals.js";
|
|
25
|
+
import { createClientPointerContext } from "./services/pointerContext.js";
|
|
26
|
+
import { EventComponentResolverRegistry } from "./Game/EventComponentResolver.js";
|
|
20
27
|
import { Howl, bootstrapCanvas, signal, trigger } from "canvasengine";
|
|
21
|
-
import { Direction, ModulesToken, PredictionController, normalizeLightingState } from "@rpgjs/common";
|
|
28
|
+
import { Direction, ModulesToken, PredictionController, Vector2, normalizeLightingState } from "@rpgjs/common";
|
|
22
29
|
import { BehaviorSubject, combineLatest, filter, lastValueFrom, switchMap, take } from "rxjs";
|
|
23
30
|
import * as PIXI from "pixi.js";
|
|
24
31
|
//#region src/RpgClientEngine.ts
|
|
25
32
|
var RpgClientEngine = class {
|
|
26
33
|
constructor(context) {
|
|
27
34
|
this.context = context;
|
|
35
|
+
this.sceneMapComponent = __ce_component$1;
|
|
28
36
|
this.stopProcessingInput = false;
|
|
29
37
|
this.width = signal("100%");
|
|
30
38
|
this.height = signal("100%");
|
|
31
39
|
this.spritesheets = /* @__PURE__ */ new Map();
|
|
32
40
|
this.sounds = /* @__PURE__ */ new Map();
|
|
33
41
|
this.componentAnimations = [];
|
|
42
|
+
this.clientVisuals = new ClientVisualRegistry();
|
|
43
|
+
this.pointer = createClientPointerContext();
|
|
34
44
|
this.particleSettings = { emitters: [] };
|
|
35
45
|
this.playerIdSignal = signal(null);
|
|
36
46
|
this.spriteComponentsBehind = signal([]);
|
|
37
47
|
this.spriteComponentsInFront = signal([]);
|
|
38
48
|
this.spriteComponents = /* @__PURE__ */ new Map();
|
|
49
|
+
this.eventComponentResolvers = new EventComponentResolverRegistry();
|
|
39
50
|
this.cameraFollowTargetId = signal(null);
|
|
40
51
|
this.mapShakeTrigger = trigger();
|
|
41
52
|
this.controlsReady = signal(void 0);
|
|
@@ -46,6 +57,7 @@ var RpgClientEngine = class {
|
|
|
46
57
|
this.pendingPredictionFrames = [];
|
|
47
58
|
this.lastClientPhysicsStepAt = 0;
|
|
48
59
|
this.frameOffset = 0;
|
|
60
|
+
this.latestServerTickAt = 0;
|
|
49
61
|
this.rtt = 0;
|
|
50
62
|
this.pingInterval = null;
|
|
51
63
|
this.PING_INTERVAL_MS = 5e3;
|
|
@@ -59,12 +71,16 @@ var RpgClientEngine = class {
|
|
|
59
71
|
this.playersReceived$ = new BehaviorSubject(false);
|
|
60
72
|
this.eventsReceived$ = new BehaviorSubject(false);
|
|
61
73
|
this.sceneResetQueued = false;
|
|
74
|
+
this.mapTransitionInProgress = false;
|
|
75
|
+
this.socketListenersInitialized = false;
|
|
62
76
|
this.tickSubscriptions = [];
|
|
77
|
+
this.pendingSyncPackets = [];
|
|
63
78
|
this.notificationManager = new NotificationManager();
|
|
64
79
|
this.webSocket = inject(WebSocketToken);
|
|
65
80
|
this.guiService = inject(RpgGui);
|
|
66
81
|
this.loadMapService = inject(LoadMapToken);
|
|
67
82
|
this.hooks = inject(ModulesToken);
|
|
83
|
+
this.projectiles = new ProjectileManager(this.hooks, (projectile) => this.predictProjectileImpact(projectile));
|
|
68
84
|
this.globalConfig = inject(GlobalConfigToken);
|
|
69
85
|
if (!this.globalConfig) this.globalConfig = {};
|
|
70
86
|
if (!this.globalConfig.box) this.globalConfig.box = {
|
|
@@ -78,12 +94,12 @@ var RpgClientEngine = class {
|
|
|
78
94
|
id: "animation",
|
|
79
95
|
component: PrebuiltComponentAnimations.Animation
|
|
80
96
|
});
|
|
81
|
-
this.registerSpriteComponent("rpg:text", __ce_component$
|
|
82
|
-
this.registerSpriteComponent("rpg:hpBar", __ce_component$
|
|
83
|
-
this.registerSpriteComponent("rpg:spBar", __ce_component$
|
|
84
|
-
this.registerSpriteComponent("rpg:bar", __ce_component$
|
|
85
|
-
this.registerSpriteComponent("rpg:shape", __ce_component$
|
|
86
|
-
this.registerSpriteComponent("rpg:image", __ce_component$
|
|
97
|
+
this.registerSpriteComponent("rpg:text", __ce_component$2);
|
|
98
|
+
this.registerSpriteComponent("rpg:hpBar", __ce_component$3);
|
|
99
|
+
this.registerSpriteComponent("rpg:spBar", __ce_component$3);
|
|
100
|
+
this.registerSpriteComponent("rpg:bar", __ce_component$3);
|
|
101
|
+
this.registerSpriteComponent("rpg:shape", __ce_component$4);
|
|
102
|
+
this.registerSpriteComponent("rpg:image", __ce_component$5);
|
|
87
103
|
this.predictionEnabled = this.globalConfig?.prediction?.enabled !== false;
|
|
88
104
|
this.initializePredictionController();
|
|
89
105
|
}
|
|
@@ -132,13 +148,26 @@ var RpgClientEngine = class {
|
|
|
132
148
|
this.sceneMap = new RpgClientMap();
|
|
133
149
|
this.sceneMap.configureClientPrediction(this.predictionEnabled);
|
|
134
150
|
this.sceneMap.loadPhysic();
|
|
151
|
+
this.resolveSceneMapComponent();
|
|
152
|
+
inject(SaveClientService).initialize();
|
|
153
|
+
this.initListeners();
|
|
154
|
+
this.guiService._initialize();
|
|
155
|
+
try {
|
|
156
|
+
await this.webSocket.connection();
|
|
157
|
+
} catch (error) {
|
|
158
|
+
this.stopPingPong();
|
|
159
|
+
await this.callConnectError(error);
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
135
162
|
this.selector = document.body.querySelector("#rpg");
|
|
136
163
|
const bootstrapOptions = this.globalConfig?.bootstrapCanvasOptions;
|
|
137
164
|
const { app, canvasElement } = await bootstrapCanvas(this.selector, __ce_component, bootstrapOptions);
|
|
138
165
|
this.canvasApp = app;
|
|
139
166
|
this.canvasElement = canvasElement;
|
|
140
167
|
this.renderer = app.renderer;
|
|
168
|
+
this.setupPointerTracking();
|
|
141
169
|
this.tick = canvasElement?.propObservables?.context["tick"].observable;
|
|
170
|
+
this.flushPendingSyncPackets();
|
|
142
171
|
const inputCheckSubscription = this.tick.subscribe(() => {
|
|
143
172
|
if (Date.now() - this.lastInputTime > 100) {
|
|
144
173
|
const player = this.getCurrentPlayer();
|
|
@@ -156,6 +185,8 @@ var RpgClientEngine = class {
|
|
|
156
185
|
this.hooks.callHooks("client-gui-load", this).subscribe();
|
|
157
186
|
this.hooks.callHooks("client-particles-load", this).subscribe();
|
|
158
187
|
this.hooks.callHooks("client-componentAnimations-load", this).subscribe();
|
|
188
|
+
this.hooks.callHooks("client-clientVisuals-load", this).subscribe();
|
|
189
|
+
this.hooks.callHooks("client-projectiles-load", this).subscribe();
|
|
159
190
|
this.hooks.callHooks("client-sprite-load", this).subscribe();
|
|
160
191
|
await lastValueFrom(this.hooks.callHooks("client-engine-onStart", this));
|
|
161
192
|
this.resizeHandler = () => {
|
|
@@ -164,6 +195,7 @@ var RpgClientEngine = class {
|
|
|
164
195
|
window.addEventListener("resize", this.resizeHandler);
|
|
165
196
|
const tickSubscription = this.tick.subscribe((tick) => {
|
|
166
197
|
this.stepClientPhysicsTick();
|
|
198
|
+
this.projectiles.step();
|
|
167
199
|
this.flushPendingPredictedStates();
|
|
168
200
|
this.flushPendingMovePath();
|
|
169
201
|
this.hooks.callHooks("client-engine-onStep", this, tick).subscribe();
|
|
@@ -174,12 +206,46 @@ var RpgClientEngine = class {
|
|
|
174
206
|
}
|
|
175
207
|
});
|
|
176
208
|
this.tickSubscriptions.push(tickSubscription);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
209
|
+
this.startPingPong();
|
|
210
|
+
}
|
|
211
|
+
resolveSceneMapComponent() {
|
|
212
|
+
const components = this.hooks.getHookFunctions("client-sceneMap-component");
|
|
213
|
+
const component = components[components.length - 1];
|
|
214
|
+
if (component) this.sceneMapComponent = component;
|
|
215
|
+
}
|
|
216
|
+
setupPointerTracking() {
|
|
217
|
+
const renderer = this.renderer;
|
|
218
|
+
const canvas = renderer?.canvas ?? renderer?.view ?? this.canvasApp?.canvas;
|
|
219
|
+
if (!canvas || typeof canvas.addEventListener !== "function") return;
|
|
220
|
+
this.pointerCanvas = canvas;
|
|
221
|
+
this.pointerMoveHandler = (event) => {
|
|
222
|
+
const rect = canvas.getBoundingClientRect();
|
|
223
|
+
const screen = {
|
|
224
|
+
x: event.clientX - rect.left,
|
|
225
|
+
y: event.clientY - rect.top
|
|
226
|
+
};
|
|
227
|
+
const viewport = this.findViewportInstance();
|
|
228
|
+
let world = screen;
|
|
229
|
+
if (viewport && typeof viewport.toWorld === "function") {
|
|
230
|
+
const point = viewport.toWorld(screen.x, screen.y);
|
|
231
|
+
world = {
|
|
232
|
+
x: Number(point.x),
|
|
233
|
+
y: Number(point.y)
|
|
234
|
+
};
|
|
235
|
+
} else if (viewport && typeof viewport.toLocal === "function") {
|
|
236
|
+
const point = viewport.toLocal(screen);
|
|
237
|
+
world = {
|
|
238
|
+
x: Number(point.x),
|
|
239
|
+
y: Number(point.y)
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
this.pointer.update(screen, world);
|
|
243
|
+
};
|
|
244
|
+
canvas.addEventListener("pointermove", this.pointerMoveHandler);
|
|
245
|
+
canvas.addEventListener("pointerdown", this.pointerMoveHandler);
|
|
246
|
+
}
|
|
247
|
+
findViewportInstance() {
|
|
248
|
+
return (this.canvasApp?.stage?.children ?? []).find((child) => typeof child?.toWorld === "function" || child?.constructor?.name === "Viewport");
|
|
183
249
|
}
|
|
184
250
|
prepareSyncPayload(data) {
|
|
185
251
|
const payload = { ...data ?? {} };
|
|
@@ -213,43 +279,27 @@ var RpgClientEngine = class {
|
|
|
213
279
|
};
|
|
214
280
|
}
|
|
215
281
|
initListeners() {
|
|
282
|
+
if (this.socketListenersInitialized) return;
|
|
283
|
+
this.socketListenersInitialized = true;
|
|
216
284
|
this.webSocket.on("sync", (data) => {
|
|
217
|
-
if (
|
|
218
|
-
this.
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
if (this.sceneResetQueued) {
|
|
222
|
-
this.sceneMap.reset();
|
|
223
|
-
this.sceneMap.loadPhysic();
|
|
224
|
-
this.sceneResetQueued = false;
|
|
225
|
-
}
|
|
226
|
-
this.hooks.callHooks("client-sceneMap-onChanges", this.sceneMap, { partial: data }).subscribe();
|
|
227
|
-
const ack = data?.ack;
|
|
228
|
-
const normalizedAck = ack && typeof ack.frame === "number" ? this.normalizeAckWithSyncState(ack, data) : void 0;
|
|
229
|
-
const payload = this.prepareSyncPayload(data);
|
|
230
|
-
load(this.sceneMap, payload, true);
|
|
231
|
-
if (normalizedAck) this.applyServerAck(normalizedAck);
|
|
232
|
-
for (const playerId in payload.players ?? {}) {
|
|
233
|
-
const player = payload.players[playerId];
|
|
234
|
-
if (!player._param) continue;
|
|
235
|
-
for (const param in player._param) this.sceneMap.players()[playerId]._param()[param] = player._param[param];
|
|
285
|
+
if (!this.tick) {
|
|
286
|
+
this.pendingSyncPackets.push(data);
|
|
287
|
+
return;
|
|
236
288
|
}
|
|
237
|
-
|
|
238
|
-
if (players && Object.keys(players).length > 0) this.playersReceived$.next(true);
|
|
239
|
-
if ((payload.events || this.sceneMap.events()) !== void 0) this.eventsReceived$.next(true);
|
|
289
|
+
this.applySyncPacket(data);
|
|
240
290
|
});
|
|
241
291
|
this.webSocket.on("pong", (data) => {
|
|
242
292
|
const now = Date.now();
|
|
243
293
|
this.rtt = now - data.clientTime;
|
|
244
294
|
const estimatedTicksInFlight = Math.floor(this.rtt / 2 / (1e3 / 60));
|
|
245
295
|
const estimatedServerTickNow = data.serverTick + estimatedTicksInFlight;
|
|
296
|
+
this.updateServerTickEstimate(estimatedServerTickNow, now);
|
|
246
297
|
if (this.inputFrameCounter > 0) this.frameOffset = estimatedServerTickNow - data.clientFrame;
|
|
247
298
|
console.debug(`[Ping/Pong] RTT: ${this.rtt}ms, ServerTick: ${data.serverTick}, FrameOffset: ${this.frameOffset}`);
|
|
248
299
|
});
|
|
249
300
|
this.webSocket.on("changeMap", (data) => {
|
|
250
|
-
|
|
251
|
-
this.
|
|
252
|
-
this.cameraFollowTargetId.set(null);
|
|
301
|
+
const nextMapId = typeof data?.mapId === "string" ? data.mapId : void 0;
|
|
302
|
+
this.beginMapTransfer(nextMapId);
|
|
253
303
|
const transferToken = typeof data?.transferToken === "string" ? data.transferToken : void 0;
|
|
254
304
|
this.loadScene(data.mapId, transferToken);
|
|
255
305
|
});
|
|
@@ -259,6 +309,29 @@ var RpgClientEngine = class {
|
|
|
259
309
|
const player = object ? this.sceneMap.getObjectById(object) : void 0;
|
|
260
310
|
this.getComponentAnimation(id).displayEffect(params, player || position);
|
|
261
311
|
});
|
|
312
|
+
this.webSocket.on("clientVisual", (data) => {
|
|
313
|
+
this.playClientVisual(data);
|
|
314
|
+
});
|
|
315
|
+
this.webSocket.on("projectile:spawnBatch", (data) => {
|
|
316
|
+
if (!this.shouldProcessProjectilePacket(data)) return;
|
|
317
|
+
this.projectiles.spawnBatch(data?.projectiles ?? [], {
|
|
318
|
+
mapId: data?.mapId,
|
|
319
|
+
currentServerTick: this.estimateServerTick(),
|
|
320
|
+
tickDurationMs: this.getPhysicsTickDurationMs()
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
this.webSocket.on("projectile:impactBatch", (data) => {
|
|
324
|
+
if (!this.shouldProcessProjectilePacket(data)) return;
|
|
325
|
+
this.projectiles.impactBatch(data?.impacts ?? [], { mapId: data?.mapId });
|
|
326
|
+
});
|
|
327
|
+
this.webSocket.on("projectile:destroyBatch", (data) => {
|
|
328
|
+
if (!this.shouldProcessProjectilePacket(data)) return;
|
|
329
|
+
this.projectiles.destroyBatch(data?.projectiles ?? [], { mapId: data?.mapId });
|
|
330
|
+
});
|
|
331
|
+
this.webSocket.on("projectile:clear", (data) => {
|
|
332
|
+
if (!this.shouldProcessProjectilePacket(data)) return;
|
|
333
|
+
this.projectiles.clear();
|
|
334
|
+
});
|
|
262
335
|
this.webSocket.on("notification", (data) => {
|
|
263
336
|
this.notificationManager.add(data);
|
|
264
337
|
});
|
|
@@ -346,9 +419,71 @@ var RpgClientEngine = class {
|
|
|
346
419
|
this.stopPingPong();
|
|
347
420
|
});
|
|
348
421
|
this.webSocket.on("error", (error) => {
|
|
349
|
-
this.
|
|
422
|
+
this.callConnectError(error);
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
beginMapTransfer(nextMapId) {
|
|
426
|
+
this.mapTransitionInProgress = true;
|
|
427
|
+
this.currentMapRoomId = nextMapId;
|
|
428
|
+
this.sceneResetQueued = false;
|
|
429
|
+
this.clearClientPredictionStates();
|
|
430
|
+
this.sceneMap.weatherState.set(null);
|
|
431
|
+
this.sceneMap.lightingState.set(null);
|
|
432
|
+
this.sceneMap.clearLightSpots();
|
|
433
|
+
this.clearComponentAnimations();
|
|
434
|
+
this.projectiles.setMapId(nextMapId);
|
|
435
|
+
this.cameraFollowTargetId.set(null);
|
|
436
|
+
this.sceneMap.reset();
|
|
437
|
+
this.sceneMap.loadPhysic();
|
|
438
|
+
}
|
|
439
|
+
clearComponentAnimations() {
|
|
440
|
+
this.componentAnimations.forEach((componentAnimation) => {
|
|
441
|
+
componentAnimation.instance?.clear?.();
|
|
350
442
|
});
|
|
351
443
|
}
|
|
444
|
+
shouldProcessProjectilePacket(data) {
|
|
445
|
+
if (this.mapTransitionInProgress) return false;
|
|
446
|
+
const packetMapId = normalizeRoomMapId(typeof data?.mapId === "string" ? data.mapId : void 0);
|
|
447
|
+
const currentMapId = normalizeRoomMapId(this.currentMapRoomId);
|
|
448
|
+
return !packetMapId || !currentMapId || packetMapId === currentMapId;
|
|
449
|
+
}
|
|
450
|
+
async callConnectError(error) {
|
|
451
|
+
await lastValueFrom(this.hooks.callHooks("client-engine-onConnectError", this, error, this.socket));
|
|
452
|
+
}
|
|
453
|
+
flushPendingSyncPackets() {
|
|
454
|
+
const packets = this.pendingSyncPackets;
|
|
455
|
+
this.pendingSyncPackets = [];
|
|
456
|
+
packets.forEach((packet) => this.applySyncPacket(packet));
|
|
457
|
+
}
|
|
458
|
+
applySyncPacket(data) {
|
|
459
|
+
if (data.pId) {
|
|
460
|
+
this.playerIdSignal.set(data.pId);
|
|
461
|
+
this.playerIdReceived$.next(true);
|
|
462
|
+
}
|
|
463
|
+
if (this.sceneResetQueued) {
|
|
464
|
+
const weatherState = this.sceneMap.weatherState();
|
|
465
|
+
const lightingState = this.sceneMap.lightingState();
|
|
466
|
+
this.sceneMap.reset();
|
|
467
|
+
this.sceneMap.weatherState.set(weatherState);
|
|
468
|
+
this.sceneMap.lightingState.set(lightingState);
|
|
469
|
+
this.sceneMap.loadPhysic();
|
|
470
|
+
this.sceneResetQueued = false;
|
|
471
|
+
}
|
|
472
|
+
this.hooks.callHooks("client-sceneMap-onChanges", this.sceneMap, { partial: data }).subscribe();
|
|
473
|
+
const ack = data?.ack;
|
|
474
|
+
const normalizedAck = ack && typeof ack.frame === "number" ? this.normalizeAckWithSyncState(ack, data) : void 0;
|
|
475
|
+
const payload = this.prepareSyncPayload(data);
|
|
476
|
+
load(this.sceneMap, payload, true);
|
|
477
|
+
if (normalizedAck) this.applyServerAck(normalizedAck);
|
|
478
|
+
for (const playerId in payload.players ?? {}) {
|
|
479
|
+
const player = payload.players[playerId];
|
|
480
|
+
if (!player._param) continue;
|
|
481
|
+
for (const param in player._param) this.sceneMap.players()[playerId]._param()[param] = player._param[param];
|
|
482
|
+
}
|
|
483
|
+
const players = payload.players || this.sceneMap.players();
|
|
484
|
+
if (players && Object.keys(players).length > 0) this.playersReceived$.next(true);
|
|
485
|
+
if ((payload.events || this.sceneMap.events()) !== void 0) this.eventsReceived$.next(true);
|
|
486
|
+
}
|
|
352
487
|
/**
|
|
353
488
|
* Start periodic ping/pong for client-server synchronization
|
|
354
489
|
*
|
|
@@ -425,11 +560,14 @@ var RpgClientEngine = class {
|
|
|
425
560
|
room: mapId,
|
|
426
561
|
query: transferToken ? { transferToken } : void 0
|
|
427
562
|
});
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
this.
|
|
432
|
-
|
|
563
|
+
try {
|
|
564
|
+
await this.webSocket.reconnect();
|
|
565
|
+
} catch (error) {
|
|
566
|
+
this.mapTransitionInProgress = false;
|
|
567
|
+
this.stopPingPong();
|
|
568
|
+
await this.callConnectError(error);
|
|
569
|
+
throw error;
|
|
570
|
+
}
|
|
433
571
|
const res = await this.loadMapService.load(mapId);
|
|
434
572
|
this.sceneMap.data.set(res);
|
|
435
573
|
if (this.playerIdSignal()) this.playerIdReceived$.next(true);
|
|
@@ -437,6 +575,8 @@ var RpgClientEngine = class {
|
|
|
437
575
|
if (players && Object.keys(players).length > 0) this.playersReceived$.next(true);
|
|
438
576
|
if (this.sceneMap.events() !== void 0) this.eventsReceived$.next(true);
|
|
439
577
|
this.mapLoadCompleted$.next(true);
|
|
578
|
+
this.currentMapRoomId = mapId;
|
|
579
|
+
this.mapTransitionInProgress = false;
|
|
440
580
|
this.sceneMap.configureClientPrediction(this.predictionEnabled);
|
|
441
581
|
this.sceneMap.loadPhysic();
|
|
442
582
|
}
|
|
@@ -859,6 +999,66 @@ var RpgClientEngine = class {
|
|
|
859
999
|
return this.spriteComponents.get(id);
|
|
860
1000
|
}
|
|
861
1001
|
/**
|
|
1002
|
+
* Register a custom event component resolver.
|
|
1003
|
+
*
|
|
1004
|
+
* The last resolver returning a component wins. This lets later modules
|
|
1005
|
+
* override earlier defaults without replacing the whole map scene.
|
|
1006
|
+
*
|
|
1007
|
+
* @param resolver - Function receiving the synced event object
|
|
1008
|
+
* @returns The registered resolver
|
|
1009
|
+
*/
|
|
1010
|
+
addEventComponentResolver(resolver) {
|
|
1011
|
+
return this.eventComponentResolvers.add(resolver);
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Resolve the custom CanvasEngine component for an event, if any.
|
|
1015
|
+
*
|
|
1016
|
+
* @param event - Synced client event object
|
|
1017
|
+
* @returns The component/config returned by the last matching resolver
|
|
1018
|
+
*/
|
|
1019
|
+
resolveEventComponent(event) {
|
|
1020
|
+
return this.eventComponentResolvers.resolve(event);
|
|
1021
|
+
}
|
|
1022
|
+
registerProjectileComponent(type, component) {
|
|
1023
|
+
return this.projectiles.register(type, component);
|
|
1024
|
+
}
|
|
1025
|
+
getProjectileComponent(type) {
|
|
1026
|
+
return this.projectiles.get(type);
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Register a named client visual macro.
|
|
1030
|
+
*
|
|
1031
|
+
* Client visuals are small client-side functions that group existing visual
|
|
1032
|
+
* primitives such as flash, sound, component animations, sprite animation, or
|
|
1033
|
+
* map shake. The server sends only the visual name and a serializable payload.
|
|
1034
|
+
*
|
|
1035
|
+
* @param name - Stable visual name sent by the server
|
|
1036
|
+
* @param handler - Client-side visual handler
|
|
1037
|
+
* @returns The registered handler
|
|
1038
|
+
*/
|
|
1039
|
+
registerClientVisual(name, handler) {
|
|
1040
|
+
return this.clientVisuals.register(name, handler);
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Register several named client visual macros.
|
|
1044
|
+
*
|
|
1045
|
+
* @param visuals - Map of visual names to client-side handlers
|
|
1046
|
+
*/
|
|
1047
|
+
registerClientVisuals(visuals) {
|
|
1048
|
+
this.clientVisuals.registerMany(visuals);
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Play a registered client visual locally.
|
|
1052
|
+
*
|
|
1053
|
+
* This is also used by the websocket listener when the server calls
|
|
1054
|
+
* `player.clientVisual()` or `map.clientVisual()`.
|
|
1055
|
+
*
|
|
1056
|
+
* @param packet - Visual name and serializable payload
|
|
1057
|
+
*/
|
|
1058
|
+
playClientVisual(packet) {
|
|
1059
|
+
return this.clientVisuals.play(packet, this);
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
862
1062
|
* Add a component animation to the engine
|
|
863
1063
|
*
|
|
864
1064
|
* Component animations are temporary visual effects that can be displayed
|
|
@@ -990,15 +1190,18 @@ var RpgClientEngine = class {
|
|
|
990
1190
|
this.emitMovePacket(input, frame, tick, timestamp, true);
|
|
991
1191
|
this.lastInputTime = Date.now();
|
|
992
1192
|
}
|
|
993
|
-
processAction(
|
|
1193
|
+
processAction(action, data) {
|
|
994
1194
|
if (this.stopProcessingInput) return;
|
|
995
1195
|
const currentPlayer = this.sceneMap.getCurrentPlayer();
|
|
996
1196
|
if (!(!currentPlayer || getCanMoveValue(currentPlayer))) return;
|
|
1197
|
+
const payload = normalizeActionInput(action, data);
|
|
997
1198
|
this.hooks.callHooks("client-engine-onInput", this, {
|
|
998
|
-
input:
|
|
1199
|
+
input: payload.action,
|
|
1200
|
+
action: payload.action,
|
|
1201
|
+
data: payload.data,
|
|
999
1202
|
playerId: this.playerId
|
|
1000
1203
|
}).subscribe();
|
|
1001
|
-
this.webSocket.emit("action",
|
|
1204
|
+
this.webSocket.emit("action", payload);
|
|
1002
1205
|
}
|
|
1003
1206
|
get PIXI() {
|
|
1004
1207
|
return PIXI;
|
|
@@ -1012,9 +1215,43 @@ var RpgClientEngine = class {
|
|
|
1012
1215
|
get scene() {
|
|
1013
1216
|
return this.sceneMap;
|
|
1014
1217
|
}
|
|
1218
|
+
getObjectById(id) {
|
|
1219
|
+
return this.sceneMap?.getObjectById(id);
|
|
1220
|
+
}
|
|
1015
1221
|
getPhysicsTick() {
|
|
1016
1222
|
return this.sceneMap?.getTick?.() ?? 0;
|
|
1017
1223
|
}
|
|
1224
|
+
getPhysicsTickDurationMs() {
|
|
1225
|
+
const timeStep = this.sceneMap?.physic?.getWorld?.()?.getTimeStep?.();
|
|
1226
|
+
return typeof timeStep === "number" && Number.isFinite(timeStep) && timeStep > 0 ? timeStep * 1e3 : 1e3 / 60;
|
|
1227
|
+
}
|
|
1228
|
+
updateServerTickEstimate(serverTick, now = Date.now()) {
|
|
1229
|
+
if (typeof serverTick !== "number" || !Number.isFinite(serverTick)) return;
|
|
1230
|
+
this.latestServerTick = serverTick;
|
|
1231
|
+
this.latestServerTickAt = now;
|
|
1232
|
+
}
|
|
1233
|
+
estimateServerTick(now = Date.now()) {
|
|
1234
|
+
if (typeof this.latestServerTick !== "number" || this.latestServerTickAt <= 0) return;
|
|
1235
|
+
const elapsedTicks = Math.max(0, (now - this.latestServerTickAt) / this.getPhysicsTickDurationMs());
|
|
1236
|
+
return this.latestServerTick + elapsedTicks;
|
|
1237
|
+
}
|
|
1238
|
+
predictProjectileImpact(projectile) {
|
|
1239
|
+
if (projectile.predictImpact === false) return null;
|
|
1240
|
+
const sceneMap = this.sceneMap;
|
|
1241
|
+
if (!sceneMap?.physic || !Number.isFinite(projectile.range) || projectile.range <= 0) return null;
|
|
1242
|
+
const origin = projectile.origin;
|
|
1243
|
+
const direction = projectile.direction;
|
|
1244
|
+
if (!origin || !direction || !Number.isFinite(origin.x) || !Number.isFinite(origin.y) || !Number.isFinite(direction.x) || !Number.isFinite(direction.y) || direction.x === 0 && direction.y === 0) return null;
|
|
1245
|
+
const hit = sceneMap.physic.raycast(new Vector2(origin.x, origin.y), new Vector2(direction.x, direction.y), projectile.range, projectile.collisionMask, (entity) => projectile.ignoreOwner === false || !projectile.ownerId || entity.uuid !== projectile.ownerId);
|
|
1246
|
+
if (!hit) return null;
|
|
1247
|
+
return {
|
|
1248
|
+
id: projectile.id,
|
|
1249
|
+
targetId: hit.entity.uuid,
|
|
1250
|
+
x: hit.point.x,
|
|
1251
|
+
y: hit.point.y,
|
|
1252
|
+
distance: hit.distance
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1018
1255
|
ensureCurrentPlayerBody() {
|
|
1019
1256
|
const player = this.sceneMap?.getCurrentPlayer();
|
|
1020
1257
|
const myId = this.playerIdSignal();
|
|
@@ -1281,6 +1518,7 @@ var RpgClientEngine = class {
|
|
|
1281
1518
|
if (sprite && typeof sprite.flash === "function") sprite.flash(options);
|
|
1282
1519
|
}
|
|
1283
1520
|
applyServerAck(ack) {
|
|
1521
|
+
this.updateServerTickEstimate(ack.serverTick);
|
|
1284
1522
|
if (this.predictionEnabled && this.prediction) {
|
|
1285
1523
|
const result = this.prediction.applyServerAck({
|
|
1286
1524
|
frame: ack.frame,
|
|
@@ -1385,6 +1623,12 @@ var RpgClientEngine = class {
|
|
|
1385
1623
|
window.removeEventListener("resize", this.resizeHandler);
|
|
1386
1624
|
this.resizeHandler = void 0;
|
|
1387
1625
|
}
|
|
1626
|
+
if (this.pointerMoveHandler && this.pointerCanvas) {
|
|
1627
|
+
this.pointerCanvas.removeEventListener("pointermove", this.pointerMoveHandler);
|
|
1628
|
+
this.pointerCanvas.removeEventListener("pointerdown", this.pointerMoveHandler);
|
|
1629
|
+
this.pointerMoveHandler = void 0;
|
|
1630
|
+
this.pointerCanvas = void 0;
|
|
1631
|
+
}
|
|
1388
1632
|
const rendererStillExists = this.renderer && typeof this.renderer.destroy === "function";
|
|
1389
1633
|
if (this.canvasApp && typeof this.canvasApp.destroy === "function") {
|
|
1390
1634
|
try {
|
|
@@ -1416,6 +1660,7 @@ var RpgClientEngine = class {
|
|
|
1416
1660
|
this.cameraFollowTargetId.set(null);
|
|
1417
1661
|
this.spriteComponentsBehind.set([]);
|
|
1418
1662
|
this.spriteComponentsInFront.set([]);
|
|
1663
|
+
this.eventComponentResolvers.clear();
|
|
1419
1664
|
this.spritesheets.clear();
|
|
1420
1665
|
this.sounds.clear();
|
|
1421
1666
|
this.componentAnimations = [];
|