@rpgjs/client 5.0.0-beta.10 → 5.0.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/Game/ProjectileManager.d.ts +89 -0
- package/dist/Game/ProjectileManager.js +179 -0
- package/dist/Game/ProjectileManager.js.map +1 -0
- package/dist/Game/ProjectileManager.spec.d.ts +1 -0
- package/dist/RpgClient.d.ts +53 -13
- package/dist/RpgClientEngine.d.ts +25 -4
- package/dist/RpgClientEngine.js +197 -48
- package/dist/RpgClientEngine.js.map +1 -1
- package/dist/components/animations/hit.ce.js.map +1 -1
- package/dist/components/character.ce.js +32 -30
- 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 +3 -0
- package/dist/index.js +9 -5
- package/dist/module.js +11 -0
- package/dist/module.js.map +1 -1
- package/dist/services/actionInput.d.ts +12 -0
- package/dist/services/actionInput.js +27 -0
- package/dist/services/actionInput.js.map +1 -0
- package/dist/services/actionInput.spec.d.ts +1 -0
- package/dist/services/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.js +3 -2
- package/dist/services/standalone.js.map +1 -1
- package/dist/services/standalone.spec.d.ts +1 -0
- package/package.json +7 -7
- package/src/Game/ProjectileManager.spec.ts +338 -0
- package/src/Game/ProjectileManager.ts +324 -0
- package/src/RpgClient.ts +62 -15
- package/src/RpgClientEngine.ts +287 -65
- package/src/components/character.ce +34 -32
- 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 +3 -0
- package/src/module.ts +13 -0
- package/src/services/actionInput.spec.ts +101 -0
- package/src/services/actionInput.ts +53 -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 +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @rpgjs/client
|
|
2
2
|
|
|
3
|
+
## 5.0.0-beta.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Add projectile runtime support with client-side prediction, action input payload handling, pointer context helpers, standalone message handling, and MMORPG connection authentication.
|
|
8
|
+
|
|
9
|
+
Add composable CanvasEngine scene map components and update built-in GUI/dynamic components for the current CanvasEngine release.
|
|
10
|
+
|
|
11
|
+
- @rpgjs/common@5.0.0-beta.11
|
|
12
|
+
- @rpgjs/server@5.0.0-beta.11
|
|
13
|
+
- @rpgjs/ui-css@5.0.0-beta.11
|
|
14
|
+
|
|
3
15
|
## 5.0.0-beta.10
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Hooks } from '@rpgjs/common';
|
|
2
|
+
export interface ClientProjectileSpawn {
|
|
3
|
+
id: string;
|
|
4
|
+
type: string;
|
|
5
|
+
ownerId?: string;
|
|
6
|
+
origin: {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
};
|
|
10
|
+
direction: {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
};
|
|
14
|
+
speed: number;
|
|
15
|
+
range: number;
|
|
16
|
+
ttl: number;
|
|
17
|
+
spawnTick: number;
|
|
18
|
+
delay?: number;
|
|
19
|
+
index?: number;
|
|
20
|
+
count?: number;
|
|
21
|
+
params?: Record<string, unknown>;
|
|
22
|
+
collisionMask?: number;
|
|
23
|
+
ignoreOwner?: boolean;
|
|
24
|
+
predictImpact?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface ClientProjectileImpact {
|
|
27
|
+
id: string;
|
|
28
|
+
targetId?: string;
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
distance?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface ClientProjectileDestroy {
|
|
34
|
+
id: string;
|
|
35
|
+
reason?: string;
|
|
36
|
+
targetId?: string;
|
|
37
|
+
x?: number;
|
|
38
|
+
y?: number;
|
|
39
|
+
distance?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface RenderedProjectileProps extends ClientProjectileSpawn {
|
|
42
|
+
x: number;
|
|
43
|
+
y: number;
|
|
44
|
+
angle: number;
|
|
45
|
+
distance: number;
|
|
46
|
+
elapsed: number;
|
|
47
|
+
progress: number;
|
|
48
|
+
impact?: ClientProjectileImpact;
|
|
49
|
+
impactElapsed?: number;
|
|
50
|
+
impactProgress?: number;
|
|
51
|
+
destroyed?: boolean;
|
|
52
|
+
}
|
|
53
|
+
export interface RenderedProjectile {
|
|
54
|
+
id: string;
|
|
55
|
+
type: string;
|
|
56
|
+
component: any;
|
|
57
|
+
props: RenderedProjectileProps;
|
|
58
|
+
}
|
|
59
|
+
export type ProjectilePredictionResolver = (projectile: ClientProjectileSpawn) => ClientProjectileImpact | null | undefined;
|
|
60
|
+
export interface ProjectileSpawnClock {
|
|
61
|
+
now?: number;
|
|
62
|
+
currentServerTick?: number;
|
|
63
|
+
tickDurationMs?: number;
|
|
64
|
+
}
|
|
65
|
+
export declare class ProjectileManager {
|
|
66
|
+
private readonly hooks;
|
|
67
|
+
private readonly predictionResolver?;
|
|
68
|
+
private readonly components;
|
|
69
|
+
private readonly projectiles;
|
|
70
|
+
private readonly version;
|
|
71
|
+
private readonly impactDurationMs;
|
|
72
|
+
constructor(hooks: Hooks, predictionResolver?: ProjectilePredictionResolver | undefined);
|
|
73
|
+
current: import('canvasengine').ComputedSignal<RenderedProjectile[]>;
|
|
74
|
+
register(type: string, component: any): any;
|
|
75
|
+
get(type: string): any;
|
|
76
|
+
spawnBatch(projectiles: ClientProjectileSpawn[], clock?: ProjectileSpawnClock): void;
|
|
77
|
+
impactBatch(impacts: ClientProjectileImpact[]): void;
|
|
78
|
+
destroyBatch(projectiles: ClientProjectileDestroy[]): void;
|
|
79
|
+
clear(): void;
|
|
80
|
+
step(): void;
|
|
81
|
+
private toProps;
|
|
82
|
+
private isWaitingForDelay;
|
|
83
|
+
private setPredictedImpact;
|
|
84
|
+
private getActivePredictedImpact;
|
|
85
|
+
private setImpact;
|
|
86
|
+
private resolveVisualImpact;
|
|
87
|
+
private isSameTarget;
|
|
88
|
+
private touch;
|
|
89
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { computed, signal } from "canvasengine";
|
|
2
|
+
//#region src/Game/ProjectileManager.ts
|
|
3
|
+
var ProjectileManager = class {
|
|
4
|
+
constructor(hooks, predictionResolver) {
|
|
5
|
+
this.hooks = hooks;
|
|
6
|
+
this.predictionResolver = predictionResolver;
|
|
7
|
+
this.components = /* @__PURE__ */ new Map();
|
|
8
|
+
this.projectiles = /* @__PURE__ */ new Map();
|
|
9
|
+
this.version = signal(0);
|
|
10
|
+
this.impactDurationMs = 350;
|
|
11
|
+
this.current = computed(() => {
|
|
12
|
+
this.version();
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
const rendered = [];
|
|
15
|
+
for (const projectile of this.projectiles.values()) {
|
|
16
|
+
const props = this.toProps(projectile, now);
|
|
17
|
+
if (!props) continue;
|
|
18
|
+
rendered.push({
|
|
19
|
+
id: projectile.spawn.id,
|
|
20
|
+
type: projectile.spawn.type,
|
|
21
|
+
component: projectile.component,
|
|
22
|
+
props
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return rendered;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
register(type, component) {
|
|
29
|
+
this.components.set(type, component);
|
|
30
|
+
return component;
|
|
31
|
+
}
|
|
32
|
+
get(type) {
|
|
33
|
+
return this.components.get(type);
|
|
34
|
+
}
|
|
35
|
+
spawnBatch(projectiles, clock = {}) {
|
|
36
|
+
const now = clock.now ?? Date.now();
|
|
37
|
+
for (const projectile of projectiles) {
|
|
38
|
+
const component = this.components.get(projectile.type);
|
|
39
|
+
if (!component) continue;
|
|
40
|
+
const runtime = {
|
|
41
|
+
spawn: {
|
|
42
|
+
...projectile,
|
|
43
|
+
delay: projectile.delay ?? 0,
|
|
44
|
+
index: projectile.index ?? 0,
|
|
45
|
+
count: projectile.count ?? 1
|
|
46
|
+
},
|
|
47
|
+
component,
|
|
48
|
+
createdAt: now
|
|
49
|
+
};
|
|
50
|
+
this.setPredictedImpact(runtime);
|
|
51
|
+
this.projectiles.set(projectile.id, runtime);
|
|
52
|
+
this.hooks.callHooks("client-projectiles-onSpawn", runtime.spawn).subscribe();
|
|
53
|
+
}
|
|
54
|
+
this.touch();
|
|
55
|
+
}
|
|
56
|
+
impactBatch(impacts) {
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
for (const impact of impacts) {
|
|
59
|
+
const projectile = this.projectiles.get(impact.id);
|
|
60
|
+
if (!projectile) continue;
|
|
61
|
+
this.setImpact(projectile, impact, now);
|
|
62
|
+
this.hooks.callHooks("client-projectiles-onImpact", this.toProps(projectile, now)).subscribe();
|
|
63
|
+
}
|
|
64
|
+
this.touch();
|
|
65
|
+
}
|
|
66
|
+
destroyBatch(projectiles) {
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
for (const destroyed of projectiles) {
|
|
69
|
+
const projectile = this.projectiles.get(destroyed.id);
|
|
70
|
+
if (!projectile) continue;
|
|
71
|
+
if (destroyed.reason === "hit") {
|
|
72
|
+
const current = this.toProps(projectile, now);
|
|
73
|
+
this.setImpact(projectile, {
|
|
74
|
+
id: destroyed.id,
|
|
75
|
+
targetId: destroyed.targetId ?? projectile.impact?.targetId,
|
|
76
|
+
x: destroyed.x ?? projectile.impact?.x ?? current?.x ?? projectile.spawn.origin.x,
|
|
77
|
+
y: destroyed.y ?? projectile.impact?.y ?? current?.y ?? projectile.spawn.origin.y,
|
|
78
|
+
distance: destroyed.distance ?? projectile.impact?.distance ?? current?.distance
|
|
79
|
+
}, now);
|
|
80
|
+
}
|
|
81
|
+
projectile.destroyReason = destroyed.reason;
|
|
82
|
+
projectile.destroyAt = projectile.destroyAt ?? (projectile.impact && projectile.impactStartedAt !== void 0 ? projectile.impactStartedAt + this.impactDurationMs : now);
|
|
83
|
+
this.hooks.callHooks("client-projectiles-onDestroy", this.toProps(projectile, now)).subscribe();
|
|
84
|
+
}
|
|
85
|
+
this.touch();
|
|
86
|
+
}
|
|
87
|
+
clear() {
|
|
88
|
+
this.projectiles.clear();
|
|
89
|
+
this.touch();
|
|
90
|
+
}
|
|
91
|
+
step() {
|
|
92
|
+
const now = Date.now();
|
|
93
|
+
let changed = false;
|
|
94
|
+
for (const [id, projectile] of this.projectiles) if (!this.toProps(projectile, now) && !this.isWaitingForDelay(projectile, now) || projectile.destroyAt !== void 0 && now >= projectile.destroyAt) {
|
|
95
|
+
this.projectiles.delete(id);
|
|
96
|
+
changed = true;
|
|
97
|
+
}
|
|
98
|
+
this.touch(changed || this.projectiles.size > 0);
|
|
99
|
+
}
|
|
100
|
+
toProps(projectile, now) {
|
|
101
|
+
const spawn = projectile.spawn;
|
|
102
|
+
const delayMs = (spawn.delay ?? 0) * 1e3;
|
|
103
|
+
const elapsedMs = now - projectile.createdAt - delayMs;
|
|
104
|
+
if (elapsedMs < 0) return null;
|
|
105
|
+
const elapsed = elapsedMs / 1e3;
|
|
106
|
+
const ttl = Math.max(.001, spawn.ttl);
|
|
107
|
+
const rawDistance = Math.min(spawn.speed * elapsed, spawn.range);
|
|
108
|
+
const predictedImpact = this.getActivePredictedImpact(projectile, now, rawDistance);
|
|
109
|
+
const visualImpact = projectile.visualImpact ?? projectile.impact;
|
|
110
|
+
const distance = visualImpact?.distance ?? predictedImpact?.distance ?? rawDistance;
|
|
111
|
+
const progress = Math.min(1, distance / spawn.range);
|
|
112
|
+
const x = visualImpact?.x ?? predictedImpact?.x ?? spawn.origin.x + spawn.direction.x * distance;
|
|
113
|
+
const y = visualImpact?.y ?? predictedImpact?.y ?? spawn.origin.y + spawn.direction.y * distance;
|
|
114
|
+
const impactElapsedMs = projectile.impactStartedAt !== void 0 ? Math.max(0, now - projectile.impactStartedAt) : void 0;
|
|
115
|
+
return {
|
|
116
|
+
...spawn,
|
|
117
|
+
x,
|
|
118
|
+
y,
|
|
119
|
+
angle: Math.atan2(spawn.direction.y, spawn.direction.x),
|
|
120
|
+
distance,
|
|
121
|
+
elapsed,
|
|
122
|
+
progress,
|
|
123
|
+
impact: projectile.impact,
|
|
124
|
+
impactElapsed: impactElapsedMs === void 0 ? void 0 : impactElapsedMs / 1e3,
|
|
125
|
+
impactProgress: impactElapsedMs === void 0 ? void 0 : Math.min(1, impactElapsedMs / this.impactDurationMs),
|
|
126
|
+
destroyed: projectile.destroyAt !== void 0,
|
|
127
|
+
ttl
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
isWaitingForDelay(projectile, now) {
|
|
131
|
+
const delayMs = (projectile.spawn.delay ?? 0) * 1e3;
|
|
132
|
+
return now - projectile.createdAt - delayMs < 0;
|
|
133
|
+
}
|
|
134
|
+
setPredictedImpact(projectile) {
|
|
135
|
+
if (projectile.spawn.predictImpact === false) return;
|
|
136
|
+
const impact = this.predictionResolver?.(projectile.spawn);
|
|
137
|
+
if (!impact || !Number.isFinite(impact.x) || !Number.isFinite(impact.y)) return;
|
|
138
|
+
const distance = typeof impact.distance === "number" && Number.isFinite(impact.distance) ? impact.distance : Math.hypot(impact.x - projectile.spawn.origin.x, impact.y - projectile.spawn.origin.y);
|
|
139
|
+
if (!Number.isFinite(distance) || distance < 0 || distance > projectile.spawn.range) return;
|
|
140
|
+
projectile.predictedImpact = {
|
|
141
|
+
...impact,
|
|
142
|
+
distance
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
getActivePredictedImpact(projectile, now, rawDistance) {
|
|
146
|
+
if (!projectile.predictedImpact || projectile.impact) return;
|
|
147
|
+
const distance = projectile.predictedImpact.distance;
|
|
148
|
+
if (distance === void 0 || rawDistance < distance) return;
|
|
149
|
+
return projectile.predictedImpact;
|
|
150
|
+
}
|
|
151
|
+
setImpact(projectile, impact, now) {
|
|
152
|
+
projectile.visualImpact = this.resolveVisualImpact(projectile, impact, now);
|
|
153
|
+
projectile.impact = impact;
|
|
154
|
+
projectile.predictedImpact = void 0;
|
|
155
|
+
projectile.impactStartedAt = projectile.impactStartedAt ?? now;
|
|
156
|
+
const impactDestroyAt = projectile.impactStartedAt + this.impactDurationMs;
|
|
157
|
+
projectile.destroyAt = Math.max(projectile.destroyAt ?? 0, impactDestroyAt);
|
|
158
|
+
}
|
|
159
|
+
resolveVisualImpact(projectile, impact, now) {
|
|
160
|
+
const predicted = projectile.predictedImpact;
|
|
161
|
+
if (!predicted || !this.isSameTarget(predicted, impact)) return impact;
|
|
162
|
+
const distance = predicted.distance;
|
|
163
|
+
if (distance === void 0) return impact;
|
|
164
|
+
const delayMs = (projectile.spawn.delay ?? 0) * 1e3;
|
|
165
|
+
const elapsedMs = now - projectile.createdAt - delayMs;
|
|
166
|
+
if (elapsedMs < 0) return impact;
|
|
167
|
+
return Math.min(projectile.spawn.speed * (elapsedMs / 1e3), projectile.spawn.range) >= distance ? predicted : impact;
|
|
168
|
+
}
|
|
169
|
+
isSameTarget(a, b) {
|
|
170
|
+
return a.targetId !== void 0 && a.targetId === b.targetId;
|
|
171
|
+
}
|
|
172
|
+
touch(force = true) {
|
|
173
|
+
if (force) this.version.update((value) => value + 1);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
//#endregion
|
|
177
|
+
export { ProjectileManager };
|
|
178
|
+
|
|
179
|
+
//# sourceMappingURL=ProjectileManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectileManager.js","names":[],"sources":["../../src/Game/ProjectileManager.ts"],"sourcesContent":["import { computed, signal } from \"canvasengine\";\nimport { Hooks } from \"@rpgjs/common\";\n\nexport interface ClientProjectileSpawn {\n id: string;\n type: string;\n ownerId?: string;\n origin: { x: number; y: number };\n direction: { x: number; y: number };\n speed: number;\n range: number;\n ttl: number;\n spawnTick: number;\n delay?: number;\n index?: number;\n count?: number;\n params?: Record<string, unknown>;\n collisionMask?: number;\n ignoreOwner?: boolean;\n predictImpact?: boolean;\n}\n\nexport interface ClientProjectileImpact {\n id: string;\n targetId?: string;\n x: number;\n y: number;\n distance?: number;\n}\n\nexport interface ClientProjectileDestroy {\n id: string;\n reason?: string;\n targetId?: string;\n x?: number;\n y?: number;\n distance?: number;\n}\n\nexport interface RenderedProjectileProps extends ClientProjectileSpawn {\n x: number;\n y: number;\n angle: number;\n distance: number;\n elapsed: number;\n progress: number;\n impact?: ClientProjectileImpact;\n impactElapsed?: number;\n impactProgress?: number;\n destroyed?: boolean;\n}\n\nexport interface RenderedProjectile {\n id: string;\n type: string;\n component: any;\n props: RenderedProjectileProps;\n}\n\nexport type ProjectilePredictionResolver = (\n projectile: ClientProjectileSpawn,\n) => ClientProjectileImpact | null | undefined;\n\nexport interface ProjectileSpawnClock {\n now?: number;\n currentServerTick?: number;\n tickDurationMs?: number;\n}\n\ninterface RuntimeProjectile {\n spawn: ClientProjectileSpawn;\n component: any;\n createdAt: number;\n impact?: ClientProjectileImpact;\n visualImpact?: ClientProjectileImpact;\n predictedImpact?: ClientProjectileImpact;\n impactStartedAt?: number;\n destroyAt?: number;\n destroyReason?: string;\n}\n\nexport class ProjectileManager {\n private readonly components = new Map<string, any>();\n private readonly projectiles = new Map<string, RuntimeProjectile>();\n private readonly version = signal(0);\n private readonly impactDurationMs = 350;\n\n constructor(\n private readonly hooks: Hooks,\n private readonly predictionResolver?: ProjectilePredictionResolver,\n ) {}\n\n current = computed<RenderedProjectile[]>(() => {\n this.version();\n const now = Date.now();\n const rendered: RenderedProjectile[] = [];\n for (const projectile of this.projectiles.values()) {\n const props = this.toProps(projectile, now);\n if (!props) {\n continue;\n }\n rendered.push({\n id: projectile.spawn.id,\n type: projectile.spawn.type,\n component: projectile.component,\n props,\n });\n }\n return rendered;\n });\n\n register(type: string, component: any): any {\n this.components.set(type, component);\n return component;\n }\n\n get(type: string): any {\n return this.components.get(type);\n }\n\n spawnBatch(projectiles: ClientProjectileSpawn[], clock: ProjectileSpawnClock = {}): void {\n const now = clock.now ?? Date.now();\n for (const projectile of projectiles) {\n const component = this.components.get(projectile.type);\n if (!component) {\n continue;\n }\n const runtime: RuntimeProjectile = {\n spawn: {\n ...projectile,\n delay: projectile.delay ?? 0,\n index: projectile.index ?? 0,\n count: projectile.count ?? 1,\n },\n component,\n createdAt: now,\n };\n this.setPredictedImpact(runtime);\n this.projectiles.set(projectile.id, runtime);\n this.hooks.callHooks(\"client-projectiles-onSpawn\", runtime.spawn).subscribe();\n }\n this.touch();\n }\n\n impactBatch(impacts: ClientProjectileImpact[]): void {\n const now = Date.now();\n for (const impact of impacts) {\n const projectile = this.projectiles.get(impact.id);\n if (!projectile) {\n continue;\n }\n this.setImpact(projectile, impact, now);\n this.hooks.callHooks(\"client-projectiles-onImpact\", this.toProps(projectile, now)).subscribe();\n }\n this.touch();\n }\n\n destroyBatch(projectiles: ClientProjectileDestroy[]): void {\n const now = Date.now();\n for (const destroyed of projectiles) {\n const projectile = this.projectiles.get(destroyed.id);\n if (!projectile) {\n continue;\n }\n if (destroyed.reason === \"hit\") {\n const current = this.toProps(projectile, now);\n this.setImpact(projectile, {\n id: destroyed.id,\n targetId: destroyed.targetId ?? projectile.impact?.targetId,\n x: destroyed.x ?? projectile.impact?.x ?? current?.x ?? projectile.spawn.origin.x,\n y: destroyed.y ?? projectile.impact?.y ?? current?.y ?? projectile.spawn.origin.y,\n distance: destroyed.distance ?? projectile.impact?.distance ?? current?.distance,\n }, now);\n }\n projectile.destroyReason = destroyed.reason;\n projectile.destroyAt = projectile.destroyAt ?? (\n projectile.impact && projectile.impactStartedAt !== undefined\n ? projectile.impactStartedAt + this.impactDurationMs\n : now\n );\n this.hooks.callHooks(\"client-projectiles-onDestroy\", this.toProps(projectile, now)).subscribe();\n }\n this.touch();\n }\n\n clear(): void {\n this.projectiles.clear();\n this.touch();\n }\n\n step(): void {\n const now = Date.now();\n let changed = false;\n for (const [id, projectile] of this.projectiles) {\n const props = this.toProps(projectile, now);\n if (\n (!props && !this.isWaitingForDelay(projectile, now)) ||\n (projectile.destroyAt !== undefined && now >= projectile.destroyAt)\n ) {\n this.projectiles.delete(id);\n changed = true;\n }\n }\n this.touch(changed || this.projectiles.size > 0);\n }\n\n private toProps(projectile: RuntimeProjectile, now: number): RenderedProjectileProps | null {\n const spawn = projectile.spawn;\n const delayMs = (spawn.delay ?? 0) * 1000;\n const elapsedMs = now - projectile.createdAt - delayMs;\n if (elapsedMs < 0) {\n return null;\n }\n const elapsed = elapsedMs / 1000;\n const ttl = Math.max(0.001, spawn.ttl);\n const rawDistance = Math.min(spawn.speed * elapsed, spawn.range);\n const predictedImpact = this.getActivePredictedImpact(projectile, now, rawDistance);\n const visualImpact = projectile.visualImpact ?? projectile.impact;\n const distance = visualImpact?.distance ?? predictedImpact?.distance ?? rawDistance;\n const progress = Math.min(1, distance / spawn.range);\n const x = visualImpact?.x ?? predictedImpact?.x ?? spawn.origin.x + spawn.direction.x * distance;\n const y = visualImpact?.y ?? predictedImpact?.y ?? spawn.origin.y + spawn.direction.y * distance;\n const impactElapsedMs = projectile.impactStartedAt !== undefined\n ? Math.max(0, now - projectile.impactStartedAt)\n : undefined;\n return {\n ...spawn,\n x,\n y,\n angle: Math.atan2(spawn.direction.y, spawn.direction.x),\n distance,\n elapsed,\n progress,\n impact: projectile.impact,\n impactElapsed: impactElapsedMs === undefined ? undefined : impactElapsedMs / 1000,\n impactProgress: impactElapsedMs === undefined\n ? undefined\n : Math.min(1, impactElapsedMs / this.impactDurationMs),\n destroyed: projectile.destroyAt !== undefined,\n ttl,\n };\n }\n\n private isWaitingForDelay(projectile: RuntimeProjectile, now: number): boolean {\n const delayMs = (projectile.spawn.delay ?? 0) * 1000;\n return now - projectile.createdAt - delayMs < 0;\n }\n\n private setPredictedImpact(projectile: RuntimeProjectile): void {\n if (projectile.spawn.predictImpact === false) {\n return;\n }\n const impact = this.predictionResolver?.(projectile.spawn);\n if (!impact || !Number.isFinite(impact.x) || !Number.isFinite(impact.y)) {\n return;\n }\n const distance = typeof impact.distance === \"number\" && Number.isFinite(impact.distance)\n ? impact.distance\n : Math.hypot(impact.x - projectile.spawn.origin.x, impact.y - projectile.spawn.origin.y);\n if (!Number.isFinite(distance) || distance < 0 || distance > projectile.spawn.range) {\n return;\n }\n projectile.predictedImpact = {\n ...impact,\n distance,\n };\n }\n\n private getActivePredictedImpact(\n projectile: RuntimeProjectile,\n now: number,\n rawDistance: number,\n ): ClientProjectileImpact | undefined {\n if (!projectile.predictedImpact || projectile.impact) {\n return undefined;\n }\n const distance = projectile.predictedImpact.distance;\n if (distance === undefined || rawDistance < distance) {\n return undefined;\n }\n return projectile.predictedImpact;\n }\n\n private setImpact(projectile: RuntimeProjectile, impact: ClientProjectileImpact, now: number): void {\n projectile.visualImpact = this.resolveVisualImpact(projectile, impact, now);\n projectile.impact = impact;\n projectile.predictedImpact = undefined;\n projectile.impactStartedAt = projectile.impactStartedAt ?? now;\n const impactDestroyAt = projectile.impactStartedAt + this.impactDurationMs;\n projectile.destroyAt = Math.max(projectile.destroyAt ?? 0, impactDestroyAt);\n }\n\n private resolveVisualImpact(\n projectile: RuntimeProjectile,\n impact: ClientProjectileImpact,\n now: number,\n ): ClientProjectileImpact {\n const predicted = projectile.predictedImpact;\n if (!predicted || !this.isSameTarget(predicted, impact)) {\n return impact;\n }\n const distance = predicted.distance;\n if (distance === undefined) {\n return impact;\n }\n const delayMs = (projectile.spawn.delay ?? 0) * 1000;\n const elapsedMs = now - projectile.createdAt - delayMs;\n if (elapsedMs < 0) {\n return impact;\n }\n const rawDistance = Math.min(projectile.spawn.speed * (elapsedMs / 1000), projectile.spawn.range);\n return rawDistance >= distance ? predicted : impact;\n }\n\n private isSameTarget(a: ClientProjectileImpact, b: ClientProjectileImpact): boolean {\n return a.targetId !== undefined && a.targetId === b.targetId;\n }\n\n private touch(force = true): void {\n if (force) {\n this.version.update((value) => value + 1);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/RpgClient.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { ComponentFunction, Signal } from 'canvasengine';
|
|
|
2
2
|
import { RpgClientEngine } from './RpgClientEngine';
|
|
3
3
|
import { Loader, Container } from 'pixi.js';
|
|
4
4
|
import { RpgClientObject } from './Game/Object';
|
|
5
|
-
import { MapPhysicsEntityContext, MapPhysicsInitContext } from '@rpgjs/common';
|
|
5
|
+
import { MapPhysicsEntityContext, MapPhysicsInitContext, RpgActionName } from '@rpgjs/common';
|
|
6
|
+
import { ClientProjectileSpawn, RenderedProjectileProps } from './Game/ProjectileManager';
|
|
6
7
|
type RpgComponent = RpgClientObject;
|
|
7
8
|
type SceneMap = Container;
|
|
8
9
|
export type SpriteComponentConfig = ComponentFunction | {
|
|
@@ -40,15 +41,18 @@ export interface RpgClientEngineHooks {
|
|
|
40
41
|
/**
|
|
41
42
|
* Recover keys from the pressed keyboard
|
|
42
43
|
*
|
|
43
|
-
* @prop { (engine: RpgClientEngine, obj: { input: string, playerId: number }) => any } [onInput]
|
|
44
|
+
* @prop { (engine: RpgClientEngine, obj: { input: string | number, action?: string | number, data?: any, playerId: number }) => any } [onInput]
|
|
44
45
|
* @memberof RpgEngineHooks
|
|
45
46
|
*/
|
|
46
47
|
onInput?: (engine: RpgClientEngine, obj: {
|
|
47
|
-
input:
|
|
48
|
+
input: RpgActionName;
|
|
49
|
+
action?: RpgActionName;
|
|
50
|
+
data?: any;
|
|
48
51
|
playerId: number;
|
|
49
52
|
}) => any;
|
|
50
53
|
/**
|
|
51
|
-
* Called when the user is connected to the server
|
|
54
|
+
* Called when the user is connected to the server. In MMORPG mode, this
|
|
55
|
+
* runs after the server sends the RPGJS connection acceptance packet.
|
|
52
56
|
*
|
|
53
57
|
* @prop { (engine: RpgClientEngine, socket: any) => any } [onConnected]
|
|
54
58
|
* @memberof RpgEngineHooks
|
|
@@ -62,7 +66,8 @@ export interface RpgClientEngineHooks {
|
|
|
62
66
|
*/
|
|
63
67
|
onDisconnect?: (engine: RpgClientEngine, reason: any, socket: any) => any;
|
|
64
68
|
/**
|
|
65
|
-
* Called when there was a connection error
|
|
69
|
+
* Called when there was a connection error. In MMORPG mode, this also runs
|
|
70
|
+
* when server-side auth refuses the connection.
|
|
66
71
|
*
|
|
67
72
|
* @prop { (engine: RpgClientEngine, err: any, socket: any) => any } [onConnectError]
|
|
68
73
|
* @memberof RpgEngineHooks
|
|
@@ -222,6 +227,13 @@ export interface RpgSceneHooks<Scene> {
|
|
|
222
227
|
onDraw?: (scene: Scene, t: number) => any;
|
|
223
228
|
}
|
|
224
229
|
export interface RpgSceneMapHooks extends RpgSceneHooks<SceneMap> {
|
|
230
|
+
/**
|
|
231
|
+
* Root CanvasEngine component used to render the RPG scene map.
|
|
232
|
+
*
|
|
233
|
+
* Use the exported `SceneMap` component inside your custom component to
|
|
234
|
+
* keep the default map rendering and compose additional scene children.
|
|
235
|
+
*/
|
|
236
|
+
component?: ComponentFunction;
|
|
225
237
|
/**
|
|
226
238
|
* The map and resources are being loaded
|
|
227
239
|
*
|
|
@@ -261,6 +273,24 @@ export interface RpgSceneMapHooks extends RpgSceneHooks<SceneMap> {
|
|
|
261
273
|
*/
|
|
262
274
|
onPhysicsReset?: (scene: SceneMap) => any;
|
|
263
275
|
}
|
|
276
|
+
export interface RpgProjectileHooks {
|
|
277
|
+
/**
|
|
278
|
+
* CanvasEngine components used to render server-authoritative projectiles.
|
|
279
|
+
*/
|
|
280
|
+
components?: Record<string, ComponentFunction>;
|
|
281
|
+
/**
|
|
282
|
+
* Called when a projectile spawn batch is received from the server.
|
|
283
|
+
*/
|
|
284
|
+
onSpawn?: (projectile: ClientProjectileSpawn) => any;
|
|
285
|
+
/**
|
|
286
|
+
* Called when the server confirms a projectile impact.
|
|
287
|
+
*/
|
|
288
|
+
onImpact?: (projectile: RenderedProjectileProps | null) => any;
|
|
289
|
+
/**
|
|
290
|
+
* Called when the server destroys a projectile.
|
|
291
|
+
*/
|
|
292
|
+
onDestroy?: (projectile: RenderedProjectileProps | null) => any;
|
|
293
|
+
}
|
|
264
294
|
export interface RpgClient {
|
|
265
295
|
/**
|
|
266
296
|
* Add hooks to the player or engine. All modules can listen to the hook
|
|
@@ -588,30 +618,33 @@ export interface RpgClient {
|
|
|
588
618
|
* */
|
|
589
619
|
sprite?: RpgSpriteHooks;
|
|
590
620
|
/**
|
|
591
|
-
* Reference the scenes of the game.
|
|
621
|
+
* Reference the scenes of the game.
|
|
592
622
|
*
|
|
593
623
|
* ```ts
|
|
594
624
|
* import { RpgSceneMapHooks, RpgClient, defineModule } from '@rpgjs/client'
|
|
625
|
+
* import MyScene from './my-scene.ce'
|
|
595
626
|
*
|
|
596
627
|
* export const sceneMap: RpgSceneMapHooks = {
|
|
597
|
-
*
|
|
628
|
+
* component: MyScene
|
|
598
629
|
* }
|
|
599
630
|
*
|
|
600
631
|
* defineModule<RpgClient>({
|
|
601
|
-
*
|
|
602
|
-
* // If you put the RpgSceneMap scene, Thhe key is called mandatory `map`
|
|
603
|
-
* map: sceneMap
|
|
604
|
-
* }
|
|
632
|
+
* sceneMap
|
|
605
633
|
* })
|
|
606
634
|
* ```
|
|
607
635
|
*
|
|
608
|
-
* @prop {
|
|
636
|
+
* @prop {RpgSceneMapHooks} [sceneMap]
|
|
609
637
|
* @memberof RpgClient
|
|
610
638
|
* */
|
|
639
|
+
sceneMap?: RpgSceneMapHooks;
|
|
640
|
+
/**
|
|
641
|
+
* Legacy scene map hook container.
|
|
642
|
+
*
|
|
643
|
+
* Prefer `sceneMap` for new code.
|
|
644
|
+
*/
|
|
611
645
|
scenes?: {
|
|
612
646
|
map: RpgSceneMapHooks;
|
|
613
647
|
};
|
|
614
|
-
sceneMap?: RpgSceneMapHooks;
|
|
615
648
|
/**
|
|
616
649
|
* Array containing the list of component animations
|
|
617
650
|
* Each element defines a temporary component to display for animations like hits, effects, etc.
|
|
@@ -642,5 +675,12 @@ export interface RpgClient {
|
|
|
642
675
|
id: string;
|
|
643
676
|
component: ComponentFunction;
|
|
644
677
|
}[];
|
|
678
|
+
/**
|
|
679
|
+
* Client-side projectile rendering configuration.
|
|
680
|
+
*
|
|
681
|
+
* Register a CanvasEngine component per projectile type. The server sends
|
|
682
|
+
* compact spawn/impact/destroy events and the client predicts x/y locally.
|
|
683
|
+
*/
|
|
684
|
+
projectiles?: RpgProjectileHooks;
|
|
645
685
|
}
|
|
646
686
|
export {};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Trigger } from 'canvasengine';
|
|
2
2
|
import { AbstractWebsocket } from './services/AbstractSocket';
|
|
3
|
-
import { Direction } from '@rpgjs/common';
|
|
3
|
+
import { Direction, RpgActionInput, RpgActionName } from '@rpgjs/common';
|
|
4
4
|
import { RpgClientMap } from './Game/Map';
|
|
5
5
|
import { AnimationManager } from './Game/AnimationManager';
|
|
6
6
|
import { Observable } from 'rxjs';
|
|
7
|
+
import { ProjectileManager } from './Game/ProjectileManager';
|
|
8
|
+
import { ClientPointerContext } from './services/pointerContext';
|
|
7
9
|
import * as PIXI from "pixi.js";
|
|
8
10
|
type ConfigurableTrigger<T> = Omit<Trigger<T>, "start"> & {
|
|
9
11
|
start(config?: T): Promise<void>;
|
|
@@ -24,12 +26,15 @@ export declare class RpgClientEngine<T = any> {
|
|
|
24
26
|
private selector;
|
|
25
27
|
globalConfig: T;
|
|
26
28
|
sceneComponent: any;
|
|
29
|
+
sceneMapComponent: any;
|
|
27
30
|
stopProcessingInput: boolean;
|
|
28
31
|
width: import('canvasengine').WritableSignal<string>;
|
|
29
32
|
height: import('canvasengine').WritableSignal<string>;
|
|
30
33
|
spritesheets: Map<string | number, any>;
|
|
31
34
|
sounds: Map<string, any>;
|
|
32
35
|
componentAnimations: any[];
|
|
36
|
+
projectiles: ProjectileManager;
|
|
37
|
+
pointer: ClientPointerContext;
|
|
33
38
|
private spritesheetResolver?;
|
|
34
39
|
private soundResolver?;
|
|
35
40
|
particleSettings: {
|
|
@@ -56,6 +61,8 @@ export declare class RpgClientEngine<T = any> {
|
|
|
56
61
|
private pendingPredictionFrames;
|
|
57
62
|
private lastClientPhysicsStepAt;
|
|
58
63
|
private frameOffset;
|
|
64
|
+
private latestServerTick?;
|
|
65
|
+
private latestServerTickAt;
|
|
59
66
|
private rtt;
|
|
60
67
|
private pingInterval;
|
|
61
68
|
private readonly PING_INTERVAL_MS;
|
|
@@ -72,6 +79,9 @@ export declare class RpgClientEngine<T = any> {
|
|
|
72
79
|
private sceneResetQueued;
|
|
73
80
|
private tickSubscriptions;
|
|
74
81
|
private resizeHandler?;
|
|
82
|
+
private pointerMoveHandler?;
|
|
83
|
+
private pointerCanvas?;
|
|
84
|
+
private pendingSyncPackets;
|
|
75
85
|
private notificationManager;
|
|
76
86
|
constructor(context: any);
|
|
77
87
|
/**
|
|
@@ -109,9 +119,15 @@ export declare class RpgClientEngine<T = any> {
|
|
|
109
119
|
*/
|
|
110
120
|
setKeyboardControls(controlInstance: any): void;
|
|
111
121
|
start(): Promise<void>;
|
|
122
|
+
private resolveSceneMapComponent;
|
|
123
|
+
private setupPointerTracking;
|
|
124
|
+
private findViewportInstance;
|
|
112
125
|
private prepareSyncPayload;
|
|
113
126
|
private normalizeAckWithSyncState;
|
|
114
127
|
private initListeners;
|
|
128
|
+
private callConnectError;
|
|
129
|
+
private flushPendingSyncPackets;
|
|
130
|
+
private applySyncPacket;
|
|
115
131
|
/**
|
|
116
132
|
* Start periodic ping/pong for client-server synchronization
|
|
117
133
|
*
|
|
@@ -486,6 +502,8 @@ export declare class RpgClientEngine<T = any> {
|
|
|
486
502
|
* @returns The CanvasEngine component, or undefined when missing
|
|
487
503
|
*/
|
|
488
504
|
getSpriteComponent(id: string): any;
|
|
505
|
+
registerProjectileComponent(type: string, component: any): any;
|
|
506
|
+
getProjectileComponent(type: string): any;
|
|
489
507
|
/**
|
|
490
508
|
* Add a component animation to the engine
|
|
491
509
|
*
|
|
@@ -564,14 +582,17 @@ export declare class RpgClientEngine<T = any> {
|
|
|
564
582
|
processInput({ input }: {
|
|
565
583
|
input: Direction;
|
|
566
584
|
}): Promise<void>;
|
|
567
|
-
processAction(
|
|
568
|
-
|
|
569
|
-
}): void;
|
|
585
|
+
processAction(action: RpgActionName, data?: any): void;
|
|
586
|
+
processAction(action: RpgActionInput): void;
|
|
570
587
|
get PIXI(): typeof PIXI;
|
|
571
588
|
get socket(): AbstractWebsocket;
|
|
572
589
|
get playerId(): string | null;
|
|
573
590
|
get scene(): RpgClientMap;
|
|
574
591
|
private getPhysicsTick;
|
|
592
|
+
private getPhysicsTickDurationMs;
|
|
593
|
+
private updateServerTickEstimate;
|
|
594
|
+
private estimateServerTick;
|
|
595
|
+
private predictProjectileImpact;
|
|
575
596
|
private ensureCurrentPlayerBody;
|
|
576
597
|
private stepClientPhysicsTick;
|
|
577
598
|
private flushPendingPredictedStates;
|