@rpgjs/client 5.0.0-beta.13 → 5.0.0-beta.15
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 +15 -0
- package/dist/Game/Map.js +15 -4
- package/dist/Game/Map.js.map +1 -1
- package/dist/Game/Object.js +4 -3
- package/dist/Game/Object.js.map +1 -1
- package/dist/RpgClientEngine.d.ts +4 -0
- package/dist/RpgClientEngine.js +22 -4
- package/dist/RpgClientEngine.js.map +1 -1
- package/dist/components/character.ce.js +2 -2
- package/dist/components/character.ce.js.map +1 -1
- package/dist/components/scenes/canvas.ce.js +54 -26
- package/dist/components/scenes/canvas.ce.js.map +1 -1
- package/dist/components/scenes/event-layer.ce.js +42 -3
- package/dist/components/scenes/event-layer.ce.js.map +1 -1
- package/dist/services/loadMap.d.ts +3 -0
- package/dist/services/loadMap.js.map +1 -1
- package/package.json +3 -3
- package/src/Game/Map.ts +37 -4
- package/src/Game/Object.ts +4 -3
- package/src/RpgClientEngine.ts +28 -4
- package/src/components/character.ce +2 -2
- package/src/components/scenes/canvas.ce +56 -24
- package/src/components/scenes/event-layer.ce +54 -2
- package/src/services/loadMap.ts +3 -1
package/src/Game/Map.ts
CHANGED
|
@@ -24,6 +24,34 @@ type TestGlobalScope = typeof globalThis & {
|
|
|
24
24
|
__RPGJS_TEST__?: boolean;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
const lightingColorsEqual = (
|
|
28
|
+
left: LightSpot["color"],
|
|
29
|
+
right: LightSpot["color"],
|
|
30
|
+
): boolean => {
|
|
31
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
32
|
+
return Array.isArray(left)
|
|
33
|
+
&& Array.isArray(right)
|
|
34
|
+
&& left.length === right.length
|
|
35
|
+
&& left.every((value, index) => value === right[index]);
|
|
36
|
+
}
|
|
37
|
+
return left === right;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const lightSpotsEqual = (left: LightSpot | undefined, right: LightSpot): boolean => {
|
|
41
|
+
if (!left) return false;
|
|
42
|
+
return left.id === right.id
|
|
43
|
+
&& left.x === right.x
|
|
44
|
+
&& left.y === right.y
|
|
45
|
+
&& left.radius === right.radius
|
|
46
|
+
&& left.intensity === right.intensity
|
|
47
|
+
&& lightingColorsEqual(left.color, right.color)
|
|
48
|
+
&& left.flicker === right.flicker
|
|
49
|
+
&& left.flickerSpeed === right.flickerSpeed
|
|
50
|
+
&& left.pulse === right.pulse
|
|
51
|
+
&& left.pulseSpeed === right.pulseSpeed
|
|
52
|
+
&& left.phase === right.phase;
|
|
53
|
+
};
|
|
54
|
+
|
|
27
55
|
export class RpgClientMap extends RpgCommonMap<any> {
|
|
28
56
|
engine: RpgClientEngine = inject(RpgClientEngine)
|
|
29
57
|
@users(RpgClientPlayer) players = signal<Record<string, RpgClientPlayer>>({});
|
|
@@ -132,10 +160,15 @@ export class RpgClientMap extends RpgCommonMap<any> {
|
|
|
132
160
|
if (!nextSpot) {
|
|
133
161
|
return;
|
|
134
162
|
}
|
|
135
|
-
this.localLightSpots.update((spots) =>
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
163
|
+
this.localLightSpots.update((spots) => {
|
|
164
|
+
if (lightSpotsEqual(spots[id], nextSpot)) {
|
|
165
|
+
return spots;
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
...spots,
|
|
169
|
+
[id]: nextSpot,
|
|
170
|
+
};
|
|
171
|
+
});
|
|
139
172
|
}
|
|
140
173
|
|
|
141
174
|
patchLightSpot(id: string, patch: Partial<LightSpot>): void {
|
package/src/Game/Object.ts
CHANGED
|
@@ -64,6 +64,7 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
64
64
|
|
|
65
65
|
constructor() {
|
|
66
66
|
super();
|
|
67
|
+
const engine = this.engine;
|
|
67
68
|
this.hooks.callHooks("client-sprite-onInit", this).subscribe();
|
|
68
69
|
|
|
69
70
|
this._frames.observable.subscribe(({ items }) => {
|
|
@@ -84,7 +85,7 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
84
85
|
const graphicRefs = Array.isArray(graphics) ? graphics : [];
|
|
85
86
|
if (graphicRefs.length === 0) return of([]);
|
|
86
87
|
return from(Promise.all(graphicRefs.map(async (graphic) => {
|
|
87
|
-
const spritesheet = await
|
|
88
|
+
const spritesheet = await engine.getSpriteSheet(graphic);
|
|
88
89
|
return withGraphicDisplayScale(spritesheet, scale);
|
|
89
90
|
})));
|
|
90
91
|
})
|
|
@@ -93,7 +94,7 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
93
94
|
this.graphicsSignals.set(sheets);
|
|
94
95
|
});
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
engine.tick
|
|
97
98
|
.pipe
|
|
98
99
|
//throttleTime(10)
|
|
99
100
|
()
|
|
@@ -101,7 +102,7 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
101
102
|
const frame = this.frames.shift();
|
|
102
103
|
if (frame) {
|
|
103
104
|
if (typeof frame.x !== "number" || typeof frame.y !== "number") return;
|
|
104
|
-
|
|
105
|
+
engine.scene.setBodyPosition(
|
|
105
106
|
this.id,
|
|
106
107
|
frame.x,
|
|
107
108
|
frame.y,
|
package/src/RpgClientEngine.ts
CHANGED
|
@@ -213,6 +213,8 @@ export class RpgClientEngine<T = any> {
|
|
|
213
213
|
private mapTransitionInProgress = false;
|
|
214
214
|
private currentMapRoomId?: string;
|
|
215
215
|
private socketListenersInitialized = false;
|
|
216
|
+
private clientReadyForMapChanges = false;
|
|
217
|
+
private pendingMapChanges: any[] = [];
|
|
216
218
|
|
|
217
219
|
// Store subscriptions and event listeners for cleanup
|
|
218
220
|
private tickSubscriptions: any[] = [];
|
|
@@ -390,6 +392,8 @@ export class RpgClientEngine<T = any> {
|
|
|
390
392
|
this.hooks.callHooks("client-sprite-load", this).subscribe();
|
|
391
393
|
|
|
392
394
|
await lastValueFrom(this.hooks.callHooks("client-engine-onStart", this));
|
|
395
|
+
this.clientReadyForMapChanges = true;
|
|
396
|
+
this.flushPendingMapChanges();
|
|
393
397
|
|
|
394
398
|
// wondow is resize
|
|
395
399
|
this.resizeHandler = () => {
|
|
@@ -594,10 +598,11 @@ export class RpgClientEngine<T = any> {
|
|
|
594
598
|
});
|
|
595
599
|
|
|
596
600
|
this.webSocket.on("changeMap", (data) => {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
+
if (!this.clientReadyForMapChanges) {
|
|
602
|
+
this.pendingMapChanges.push(data);
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
this.handleChangeMap(data);
|
|
601
606
|
});
|
|
602
607
|
|
|
603
608
|
this.webSocket.on("showComponentAnimation", (data) => {
|
|
@@ -795,6 +800,19 @@ export class RpgClientEngine<T = any> {
|
|
|
795
800
|
packets.forEach((packet) => this.applySyncPacket(packet));
|
|
796
801
|
}
|
|
797
802
|
|
|
803
|
+
private flushPendingMapChanges() {
|
|
804
|
+
const packets = this.pendingMapChanges;
|
|
805
|
+
this.pendingMapChanges = [];
|
|
806
|
+
packets.forEach((packet) => this.handleChangeMap(packet));
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
private handleChangeMap(data: any) {
|
|
810
|
+
const nextMapId = typeof data?.mapId === "string" ? data.mapId : undefined;
|
|
811
|
+
this.beginMapTransfer(nextMapId);
|
|
812
|
+
const transferToken = typeof data?.transferToken === "string" ? data.transferToken : undefined;
|
|
813
|
+
this.loadScene(data.mapId, transferToken);
|
|
814
|
+
}
|
|
815
|
+
|
|
798
816
|
private applySyncPacket(data: any) {
|
|
799
817
|
if (data.pId) {
|
|
800
818
|
this.playerIdSignal.set(data.pId);
|
|
@@ -953,6 +971,12 @@ export class RpgClientEngine<T = any> {
|
|
|
953
971
|
throw error;
|
|
954
972
|
}
|
|
955
973
|
const res = await this.loadMapService.load(mapId)
|
|
974
|
+
const loadedLighting = typeof res?.lighting !== "undefined"
|
|
975
|
+
? res.lighting
|
|
976
|
+
: res?.data?.lighting;
|
|
977
|
+
if (typeof loadedLighting !== "undefined") {
|
|
978
|
+
this.sceneMap.lightingState.set(normalizeLightingState(loadedLighting));
|
|
979
|
+
}
|
|
956
980
|
this.sceneMap.data.set(res)
|
|
957
981
|
|
|
958
982
|
// Check if playerId is already present
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
|
|
75
75
|
import { lastValueFrom, combineLatest, pairwise, filter, map, startWith } from "rxjs";
|
|
76
76
|
import { Particle } from "@canvasengine/presets";
|
|
77
|
-
import { GameEngineToken, ModulesToken } from "@rpgjs/common";
|
|
77
|
+
import { GameEngineToken, ModulesToken, shouldRenderLightingShadows } from "@rpgjs/common";
|
|
78
78
|
import { RpgClientEngine } from "../RpgClientEngine";
|
|
79
79
|
import { inject } from "../core/inject";
|
|
80
80
|
import { Direction, Animation } from "@rpgjs/common";
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
const isMe = computed(isCurrentPlayer);
|
|
114
114
|
const shadowsEnabled = computed(() => {
|
|
115
115
|
const lighting = client.sceneMap?.lighting?.();
|
|
116
|
-
return
|
|
116
|
+
return shouldRenderLightingShadows(lighting);
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
/**
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
import { RpgClientEngine } from "../../RpgClientEngine";
|
|
50
50
|
import { RpgGui } from "../../Gui/Gui";
|
|
51
51
|
import { NightAmbiant, SpriteShadows } from '@canvasengine/presets'
|
|
52
|
+
import { shouldRenderLightingShadows } from "@rpgjs/common";
|
|
52
53
|
|
|
53
54
|
const engine = inject(RpgClientEngine);
|
|
54
55
|
const SceneMap = engine.sceneMapComponent;
|
|
@@ -99,12 +100,20 @@
|
|
|
99
100
|
const NIGHT_SPOT_MIN_INTENSITY = 1
|
|
100
101
|
const SHADOW_SPOT_RADIUS_SCALE = 12
|
|
101
102
|
const SHADOW_SPOT_MIN_RADIUS = 480
|
|
102
|
-
const SHADOW_SPOT_MIN_INTENSITY = 1.35
|
|
103
103
|
|
|
104
|
+
const toFiniteNumber = (value, fallback = null) => {
|
|
105
|
+
const number = Number(value)
|
|
106
|
+
return Number.isFinite(number) ? number : fallback
|
|
107
|
+
}
|
|
108
|
+
const clampNumber = (value, min, max) => Math.max(min, Math.min(max, value))
|
|
104
109
|
const nightSpotRadius = (radius) => Math.max(radius * NIGHT_SPOT_RADIUS_SCALE, NIGHT_SPOT_MIN_RADIUS)
|
|
105
110
|
const shadowSpotRadius = (radius) => Math.max(radius * SHADOW_SPOT_RADIUS_SCALE, SHADOW_SPOT_MIN_RADIUS)
|
|
106
111
|
const nightSpotIntensity = (intensity, fallback) => Math.max(intensity ?? fallback, NIGHT_SPOT_MIN_INTENSITY)
|
|
107
|
-
const
|
|
112
|
+
const shadowLightIntensity = (intensity, fallback = 1) => {
|
|
113
|
+
const value = Number(intensity ?? fallback)
|
|
114
|
+
return Number.isFinite(value) ? Math.max(0, value) : fallback
|
|
115
|
+
}
|
|
116
|
+
const shadowSpotIntensity = (intensity) => shadowLightIntensity(intensity, 1)
|
|
108
117
|
|
|
109
118
|
const lightingAmbient = computed(() => {
|
|
110
119
|
const state = lighting?.()
|
|
@@ -128,11 +137,6 @@
|
|
|
128
137
|
}
|
|
129
138
|
})
|
|
130
139
|
})
|
|
131
|
-
const hasLightSpots = computed(() => {
|
|
132
|
-
const state = lighting?.()
|
|
133
|
-
return (state?.spots?.length ?? 0) > 0
|
|
134
|
-
})
|
|
135
|
-
|
|
136
140
|
const lightingDarkness = computed(() => {
|
|
137
141
|
const darkness = lightingAmbient().darkness
|
|
138
142
|
return typeof darkness === "number" ? darkness : 0
|
|
@@ -161,18 +165,55 @@
|
|
|
161
165
|
const scale = Number(data?.params?.scale ?? 1) || 1
|
|
162
166
|
const mapWidth = width * scale
|
|
163
167
|
const mapHeight = height * scale
|
|
168
|
+
const projectionBase = Math.max(1, mapWidth, mapHeight)
|
|
164
169
|
|
|
165
170
|
return {
|
|
166
|
-
x: -
|
|
167
|
-
y: -
|
|
171
|
+
x: -projectionBase * 24,
|
|
172
|
+
y: -projectionBase * 24,
|
|
168
173
|
z: 520,
|
|
169
|
-
radius:
|
|
174
|
+
radius: projectionBase * 160,
|
|
170
175
|
intensity: 0.85,
|
|
171
176
|
shadowWeight: lightingDarkness() > 0 ? 2.2 : 1,
|
|
172
177
|
enabled: true,
|
|
173
178
|
}
|
|
174
179
|
}
|
|
175
180
|
|
|
181
|
+
const normalizeSunDirection = (sun) => {
|
|
182
|
+
const x = toFiniteNumber(sun?.x, null)
|
|
183
|
+
const y = toFiniteNumber(sun?.y, null)
|
|
184
|
+
if (x !== null && y !== null && Math.hypot(x, y) > 0.001) {
|
|
185
|
+
return { x, y }
|
|
186
|
+
}
|
|
187
|
+
return { x: -0.45, y: -1 }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const defaultSunAmbientLight = () => {
|
|
191
|
+
const state = lighting?.()
|
|
192
|
+
if (!state?.sun || state.sun.enabled === false) return null
|
|
193
|
+
|
|
194
|
+
const defaultSun = defaultSunLight()
|
|
195
|
+
const sun = {
|
|
196
|
+
...defaultSun,
|
|
197
|
+
...state.sun,
|
|
198
|
+
intensity: shadowLightIntensity(state.sun.intensity, defaultSun.intensity),
|
|
199
|
+
shadowWeight: state.sun.shadowWeight ?? defaultSun.shadowWeight,
|
|
200
|
+
}
|
|
201
|
+
if (sun.intensity <= 0) return null
|
|
202
|
+
|
|
203
|
+
const direction = normalizeSunDirection(sun)
|
|
204
|
+
const shadowWeight = clampNumber(toFiniteNumber(sun.shadowWeight, lightingDarkness() > 0 ? 1.35 : 1) ?? 1, 0, 4)
|
|
205
|
+
const length = clampNumber(30 + sun.intensity * 38 * Math.max(0.75, shadowWeight), 30, 86)
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
x: direction.x,
|
|
209
|
+
y: direction.y,
|
|
210
|
+
z: toFiniteNumber(sun.z, 520) ?? 520,
|
|
211
|
+
intensity: clampNumber(sun.intensity, 0, 2),
|
|
212
|
+
shadowWeight,
|
|
213
|
+
length,
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
176
217
|
const shadowState = computed(() => {
|
|
177
218
|
const state = lighting?.()
|
|
178
219
|
return state?.shadows ?? null
|
|
@@ -180,12 +221,6 @@
|
|
|
180
221
|
|
|
181
222
|
const shadowLights = computed(() => {
|
|
182
223
|
const state = lighting?.()
|
|
183
|
-
const defaultSun = defaultSunLight()
|
|
184
|
-
const sun = {
|
|
185
|
-
...defaultSun,
|
|
186
|
-
...(state?.sun ?? {}),
|
|
187
|
-
shadowWeight: state?.sun?.shadowWeight ?? defaultSun.shadowWeight,
|
|
188
|
-
}
|
|
189
224
|
const spotLights = (state?.spots ?? []).map((spot) => {
|
|
190
225
|
const radius = spot.radius ?? 180
|
|
191
226
|
return {
|
|
@@ -194,15 +229,12 @@
|
|
|
194
229
|
z: 170,
|
|
195
230
|
radius: shadowSpotRadius(radius),
|
|
196
231
|
intensity: shadowSpotIntensity(spot.intensity),
|
|
197
|
-
shadowWeight:
|
|
232
|
+
shadowWeight: 1,
|
|
198
233
|
enabled: true,
|
|
199
234
|
}
|
|
200
235
|
})
|
|
201
236
|
|
|
202
|
-
return
|
|
203
|
-
...((sun.enabled === false || sun.intensity <= 0) ? [] : [sun]),
|
|
204
|
-
...spotLights,
|
|
205
|
-
]
|
|
237
|
+
return spotLights
|
|
206
238
|
})
|
|
207
239
|
|
|
208
240
|
const shadowAmbientLight = computed(() => {
|
|
@@ -210,12 +242,12 @@
|
|
|
210
242
|
if (shadows?.ambientLight === null || shadows?.ambientLight?.enabled === false) {
|
|
211
243
|
return null
|
|
212
244
|
}
|
|
213
|
-
return shadows?.ambientLight ??
|
|
245
|
+
return shadows?.ambientLight ?? defaultSunAmbientLight()
|
|
214
246
|
})
|
|
215
247
|
|
|
216
248
|
const shadowEnabled = computed(() => {
|
|
217
|
-
const
|
|
218
|
-
return Boolean((
|
|
249
|
+
const state = lighting?.()
|
|
250
|
+
return Boolean(shouldRenderLightingShadows(state) && (shadowLights().length > 0 || shadowAmbientLight()))
|
|
219
251
|
})
|
|
220
252
|
|
|
221
253
|
const shadowMode = computed(() => shadowState()?.mode ?? "strongest")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<Container sortableChildren={true}>
|
|
1
|
+
<Container sortableChildren={true} onBeforeDestroy={detachPixiChildren}>
|
|
2
2
|
@for ((event,id) of events) {
|
|
3
3
|
<Character id={id} object={event} />
|
|
4
4
|
}
|
|
@@ -13,14 +13,66 @@
|
|
|
13
13
|
</Container>
|
|
14
14
|
|
|
15
15
|
<script>
|
|
16
|
+
import { effect, mount } from "canvasengine";
|
|
16
17
|
import { inject } from "../../core/inject";
|
|
17
18
|
import { RpgClientEngine } from "../../RpgClientEngine";
|
|
18
19
|
import Character from "../character.ce";
|
|
19
20
|
import LightHalo from "../prebuilt/light-halo.ce";
|
|
20
21
|
|
|
21
22
|
const engine = inject(RpgClientEngine);
|
|
22
|
-
const { children } = defineProps(
|
|
23
|
+
const { children, pixiChildren } = defineProps({
|
|
24
|
+
pixiChildren: {
|
|
25
|
+
default: []
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
const readValue = (value) => typeof value === "function" ? value() : value
|
|
29
|
+
let rootContainer = null
|
|
30
|
+
let mountedPixiChildren = []
|
|
23
31
|
|
|
24
32
|
const players = engine.sceneMap.players
|
|
25
33
|
const events = engine.sceneMap.events
|
|
34
|
+
|
|
35
|
+
const getPixiChildren = () => {
|
|
36
|
+
const value = readValue(pixiChildren)
|
|
37
|
+
return Array.isArray(value) ? value.filter(Boolean) : []
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const syncPixiChildren = () => {
|
|
41
|
+
if (!rootContainer) return
|
|
42
|
+
const nextPixiChildren = getPixiChildren()
|
|
43
|
+
mountedPixiChildren.forEach((child) => {
|
|
44
|
+
if (!nextPixiChildren.includes(child) && child.parent === rootContainer) {
|
|
45
|
+
rootContainer.removeChild(child)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
nextPixiChildren.forEach((child) => {
|
|
49
|
+
if (child.parent === rootContainer) return
|
|
50
|
+
if (child.parent) {
|
|
51
|
+
child.parent.removeChild(child)
|
|
52
|
+
}
|
|
53
|
+
rootContainer.addChild(child)
|
|
54
|
+
})
|
|
55
|
+
mountedPixiChildren = nextPixiChildren
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const detachPixiChildren = () => {
|
|
59
|
+
if (!rootContainer) return
|
|
60
|
+
mountedPixiChildren.forEach((child) => {
|
|
61
|
+
if (child.parent === rootContainer) {
|
|
62
|
+
rootContainer.removeChild(child)
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
mountedPixiChildren = []
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
effect(() => {
|
|
69
|
+
readValue(pixiChildren)
|
|
70
|
+
syncPixiChildren()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
mount((element) => {
|
|
74
|
+
rootContainer = element.componentInstance
|
|
75
|
+
syncPixiChildren()
|
|
76
|
+
return detachPixiChildren
|
|
77
|
+
})
|
|
26
78
|
</script>
|
package/src/services/loadMap.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Context, inject } from "@signe/di";
|
|
2
|
-
import { UpdateMapToken, UpdateMapService } from "@rpgjs/common";
|
|
2
|
+
import { UpdateMapToken, UpdateMapService, type LightingState } from "@rpgjs/common";
|
|
3
3
|
|
|
4
4
|
export const LoadMapToken = 'LoadMapToken'
|
|
5
5
|
|
|
@@ -24,6 +24,8 @@ type MapData = {
|
|
|
24
24
|
positions?: Record<string, { x: number; y: number; z?: number }>;
|
|
25
25
|
/** Optional map identifier, defaults to the mapId parameter if not provided */
|
|
26
26
|
id?: string;
|
|
27
|
+
/** Optional initial lighting state for the loaded map */
|
|
28
|
+
lighting?: LightingState | null;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
/**
|