@rpgjs/client 5.0.0-beta.13 → 5.0.0-beta.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpgjs/client",
3
- "version": "5.0.0-beta.13",
3
+ "version": "5.0.0-beta.14",
4
4
  "description": "RPGJS is a framework for creating RPG/MMORPG games",
5
5
  "main": "dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -22,8 +22,8 @@
22
22
  "pixi.js": "^8.9.2"
23
23
  },
24
24
  "dependencies": {
25
- "@rpgjs/common": "5.0.0-beta.13",
26
- "@rpgjs/server": "5.0.0-beta.13",
25
+ "@rpgjs/common": "5.0.0-beta.14",
26
+ "@rpgjs/server": "5.0.0-beta.14",
27
27
  "@rpgjs/ui-css": "5.0.0-beta.12",
28
28
  "@signe/di": "3.0.1",
29
29
  "@signe/room": "3.0.1",
@@ -953,6 +953,12 @@ export class RpgClientEngine<T = any> {
953
953
  throw error;
954
954
  }
955
955
  const res = await this.loadMapService.load(mapId)
956
+ const loadedLighting = typeof res?.lighting !== "undefined"
957
+ ? res.lighting
958
+ : res?.data?.lighting;
959
+ if (typeof loadedLighting !== "undefined") {
960
+ this.sceneMap.lightingState.set(normalizeLightingState(loadedLighting));
961
+ }
956
962
  this.sceneMap.data.set(res)
957
963
 
958
964
  // 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 Boolean(lighting?.shadows?.enabled || (lighting?.spots?.length ?? 0) > 0);
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 shadowSpotIntensity = (intensity) => Math.max(intensity ?? 1.3, SHADOW_SPOT_MIN_INTENSITY)
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: -mapWidth * 0.35,
167
- y: -mapHeight * 0.45,
171
+ x: -projectionBase * 24,
172
+ y: -projectionBase * 24,
168
173
  z: 520,
169
- radius: Math.max(mapWidth, mapHeight) * 2.5,
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: 2.4,
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 ?? { x: -0.18, y: -1, z: 420, intensity: 0.32, shadowWeight: 1 }
245
+ return shadows?.ambientLight ?? defaultSunAmbientLight()
214
246
  })
215
247
 
216
248
  const shadowEnabled = computed(() => {
217
- const shadows = shadowState()
218
- return Boolean((shadows?.enabled || hasLightSpots()) && (shadowLights().length > 0 || shadowAmbientLight()))
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>
@@ -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
  /**