@jgengine/shell 0.9.0 → 0.11.0
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 +34 -3
- package/LICENSE +203 -661
- package/README.md +1 -1
- package/dist/GamePhaseStamp.d.ts +1 -0
- package/dist/GamePhaseStamp.js +9 -0
- package/dist/GamePlayerShell.d.ts +4 -31
- package/dist/GamePlayerShell.js +162 -932
- package/dist/GameUiPreview.js +0 -2
- package/dist/audio/audioEngine.d.ts +8 -0
- package/dist/audio/audioEngine.js +63 -8
- package/dist/audio/audioWire.d.ts +22 -0
- package/dist/audio/audioWire.js +19 -0
- package/dist/audio/musicDirector.d.ts +31 -0
- package/dist/audio/musicDirector.js +125 -0
- package/dist/audio/musicVoices.d.ts +10 -0
- package/dist/audio/musicVoices.js +338 -0
- package/dist/audio/synthEngine.d.ts +9 -0
- package/dist/audio/synthEngine.js +57 -0
- package/dist/behaviour.d.ts +2 -1
- package/dist/behaviour.js +2 -1
- package/dist/behaviourAttach.d.ts +1 -0
- package/dist/behaviourAttach.js +1 -0
- package/dist/behaviourDriver.d.ts +1 -0
- package/dist/behaviourDriver.js +1 -0
- package/dist/boundActionDispatch.d.ts +13 -0
- package/dist/boundActionDispatch.js +37 -0
- package/dist/camera/GameCameraRig.d.ts +5 -2
- package/dist/camera/GameCameraRig.js +2 -2
- package/dist/camera/GameFirstPersonCamera.d.ts +12 -2
- package/dist/camera/GameFirstPersonCamera.js +29 -7
- package/dist/camera/GameInspectionCamera.d.ts +1 -1
- package/dist/camera/GameInspectionCamera.js +13 -2
- package/dist/camera/GameOrbitCamera.js +59 -10
- package/dist/camera/cameraBlendMath.d.ts +3 -0
- package/dist/camera/cameraBlendMath.js +3 -0
- package/dist/camera/fovPreference.d.ts +6 -1
- package/dist/camera/fovPreference.js +6 -1
- package/dist/camera/index.d.ts +4 -7
- package/dist/camera/index.js +4 -7
- package/dist/camera/inspectionCameraMath.d.ts +7 -2
- package/dist/camera/inspectionCameraMath.js +7 -2
- package/dist/camera/orbitCameraMath.d.ts +54 -6
- package/dist/camera/orbitCameraMath.js +61 -6
- package/dist/camera/rigMath.d.ts +94 -24
- package/dist/camera/rigMath.js +100 -24
- package/dist/camera/rigResolve.d.ts +4 -2
- package/dist/camera/rigResolve.js +4 -2
- package/dist/camera/shakeChannel.d.ts +6 -2
- package/dist/camera/shakeChannel.js +6 -2
- package/dist/camera/shakeChannelMath.d.ts +1 -0
- package/dist/camera/shakeChannelMath.js +1 -0
- package/dist/cartridge/runtime.d.ts +15 -0
- package/dist/cartridge/runtime.js +556 -0
- package/dist/cartridge/spec.d.ts +188 -0
- package/dist/cartridge/spec.js +19 -0
- package/dist/cartridge/validate.d.ts +2 -0
- package/dist/cartridge/validate.js +78 -0
- package/dist/cartridge.d.ts +1 -1
- package/dist/cartridge.js +4 -3
- package/dist/commandSink.d.ts +25 -0
- package/dist/commandSink.js +32 -0
- package/dist/defineGame.js +26 -5
- package/dist/devtools/ColPanel.d.ts +1 -0
- package/dist/devtools/ColPanel.js +14 -0
- package/dist/devtools/CollisionDebugWorld.js +1 -0
- package/dist/devtools/DevtoolsOverlay.d.ts +7 -3
- package/dist/devtools/DevtoolsOverlay.js +19 -450
- package/dist/devtools/KeysPanel.d.ts +4 -0
- package/dist/devtools/KeysPanel.js +31 -0
- package/dist/devtools/LogsPanel.d.ts +1 -0
- package/dist/devtools/LogsPanel.js +12 -0
- package/dist/devtools/NetPanel.d.ts +4 -0
- package/dist/devtools/NetPanel.js +10 -0
- package/dist/devtools/PerfPanel.d.ts +4 -0
- package/dist/devtools/PerfPanel.js +53 -0
- package/dist/devtools/TunePanel.d.ts +3 -0
- package/dist/devtools/TunePanel.js +309 -0
- package/dist/devtools/agentBridge.d.ts +41 -0
- package/dist/devtools/agentBridge.js +262 -0
- package/dist/devtools/collisionDebug.d.ts +6 -0
- package/dist/devtools/collisionDebug.js +6 -0
- package/dist/devtools/collisionDebugMath.d.ts +9 -2
- package/dist/devtools/collisionDebugMath.js +25 -5
- package/dist/devtools/devtoolsOverrides.d.ts +6 -0
- package/dist/devtools/devtoolsOverrides.js +48 -0
- package/dist/devtools/panelAtoms.d.ts +9 -0
- package/dist/devtools/panelAtoms.js +10 -0
- package/dist/devtools/perfDiagnose.d.ts +2 -0
- package/dist/devtools/perfDiagnose.js +43 -0
- package/dist/diagnostics/RuntimeDiagnostics.d.ts +29 -0
- package/dist/diagnostics/RuntimeDiagnostics.js +69 -0
- package/dist/drivers/FrameDriver.d.ts +37 -0
- package/dist/drivers/FrameDriver.js +232 -0
- package/dist/drivers/HudOnlyDriver.d.ts +16 -0
- package/dist/drivers/HudOnlyDriver.js +60 -0
- package/dist/environment/Daylight.d.ts +29 -7
- package/dist/environment/Daylight.js +153 -13
- package/dist/environment/EnvironmentScene.js +42 -7
- package/dist/environment/RoadRibbons.d.ts +7 -0
- package/dist/environment/RoadRibbons.js +57 -0
- package/dist/environment/VolumetricClouds.d.ts +17 -0
- package/dist/environment/VolumetricClouds.js +202 -0
- package/dist/environment/daylightCycle.d.ts +3 -1
- package/dist/environment/daylightCycle.js +3 -1
- package/dist/environment/groundPadMath.d.ts +3 -0
- package/dist/environment/groundPadMath.js +3 -0
- package/dist/environment/index.d.ts +1 -0
- package/dist/environment/index.js +1 -0
- package/dist/environment/skyLightingPolicy.d.ts +2 -0
- package/dist/environment/skyLightingPolicy.js +2 -0
- package/dist/hotbarActions.d.ts +26 -0
- package/dist/hotbarActions.js +27 -0
- package/dist/inputSink.d.ts +30 -0
- package/dist/inputSink.js +85 -0
- package/dist/materialOverride.d.ts +10 -0
- package/dist/materialOverride.js +15 -3
- package/dist/multiplayer.js +14 -4
- package/dist/postfx/PostProcessing.d.ts +10 -0
- package/dist/postfx/PostProcessing.js +120 -0
- package/dist/postfx/gradeShader.d.ts +4 -0
- package/dist/postfx/gradeShader.js +72 -0
- package/dist/registry.d.ts +3 -2
- package/dist/render/EnvironmentLighting.d.ts +10 -0
- package/dist/render/EnvironmentLighting.js +34 -0
- package/dist/render/SceneLighting.d.ts +7 -0
- package/dist/render/SceneLighting.js +18 -0
- package/dist/render/SceneModels.d.ts +14 -0
- package/dist/render/SceneModels.js +337 -0
- package/dist/render/defaultSurface.d.ts +29 -0
- package/dist/render/defaultSurface.js +101 -0
- package/dist/render/modelLoad.d.ts +24 -0
- package/dist/render/modelLoad.js +73 -0
- package/dist/render/modelRender.d.ts +11 -0
- package/dist/render/modelRender.js +15 -0
- package/dist/render/resolveModel.d.ts +39 -3
- package/dist/render/resolveModel.js +79 -2
- package/dist/render/useEntityRenderCues.d.ts +13 -0
- package/dist/render/useEntityRenderCues.js +62 -0
- package/dist/scatter/InstancedScatter.d.ts +24 -0
- package/dist/scatter/InstancedScatter.js +130 -0
- package/dist/scatter/scatterModels.d.ts +25 -0
- package/dist/scatter/scatterModels.js +61 -0
- package/dist/scatter/scatterProxies.d.ts +6 -0
- package/dist/scatter/scatterProxies.js +94 -0
- package/dist/scatter.d.ts +1 -0
- package/dist/scatter.js +1 -0
- package/dist/scene/AuthoredScene.d.ts +82 -0
- package/dist/scene/AuthoredScene.js +173 -0
- package/dist/scene/GeneratedAssetRenderer.d.ts +14 -0
- package/dist/scene/GeneratedAssetRenderer.js +59 -0
- package/dist/scene/GrassFieldRenderer.d.ts +2 -0
- package/dist/scene/GrassFieldRenderer.js +25 -0
- package/dist/scene/SoilPatchRenderer.d.ts +2 -0
- package/dist/scene/SoilPatchRenderer.js +21 -0
- package/dist/scene/StudioStage.d.ts +37 -0
- package/dist/scene/StudioStage.js +42 -0
- package/dist/scene/WaterRenderer.d.ts +2 -0
- package/dist/scene/WaterRenderer.js +37 -0
- package/dist/scene/builtinSceneKindRenderers.d.ts +2 -0
- package/dist/scene/builtinSceneKindRenderers.js +22 -0
- package/dist/scene/sceneKindRenderers.d.ts +28 -0
- package/dist/scene/sceneKindRenderers.js +17 -0
- package/dist/scene.d.ts +1 -0
- package/dist/scene.js +1 -0
- package/dist/settings/appliedSettings.d.ts +5 -0
- package/dist/settings/appliedSettings.js +9 -0
- package/dist/settings/settingsController.d.ts +2 -0
- package/dist/settings/settingsController.js +22 -1
- package/dist/shellConstants.d.ts +4 -0
- package/dist/shellConstants.js +4 -0
- package/dist/shellMovement.d.ts +8 -0
- package/dist/shellMovement.js +11 -0
- package/dist/shellPointer.d.ts +20 -0
- package/dist/shellPointer.js +28 -0
- package/dist/structures/PlacementGhost.d.ts +4 -0
- package/dist/structures/PlacementGhost.js +4 -0
- package/dist/structures/TransformGizmo.d.ts +43 -0
- package/dist/structures/TransformGizmo.js +55 -0
- package/dist/structures/index.d.ts +1 -0
- package/dist/structures/index.js +1 -0
- package/dist/terrain/CarvedTerrain.d.ts +4 -1
- package/dist/terrain/CarvedTerrain.js +4 -3
- package/dist/terrain/GrassField.js +1 -1
- package/dist/terrain/TerraformBrushCursor.js +3 -0
- package/dist/terrain/grassBudget.d.ts +11 -2
- package/dist/terrain/grassBudget.js +20 -4
- package/dist/terrain/grassGeometry.d.ts +3 -0
- package/dist/terrain/grassGeometry.js +3 -0
- package/dist/terrain/grassMaterial.d.ts +2 -0
- package/dist/terrain/grassMaterial.js +2 -0
- package/dist/terrain/random.d.ts +3 -0
- package/dist/terrain/random.js +3 -0
- package/dist/terrain/soilPatchMaterial.d.ts +10 -0
- package/dist/terrain/soilPatchMaterial.js +109 -0
- package/dist/terrain/terrainDetailMaterial.d.ts +31 -0
- package/dist/terrain/terrainDetailMaterial.js +157 -0
- package/dist/terrain/terrainMath.d.ts +8 -1
- package/dist/terrain/terrainMath.js +8 -1
- package/dist/touch/TouchControlsOverlay.d.ts +9 -5
- package/dist/touch/TouchControlsOverlay.js +211 -22
- package/dist/useShellMultiplayerSync.d.ts +9 -0
- package/dist/useShellMultiplayerSync.js +98 -0
- package/dist/visibility/CullingProvider.js +1 -1
- package/dist/water/OceanConfig.d.ts +2 -0
- package/dist/water/OceanConfig.js +2 -0
- package/dist/weather/fireSpreadPose.d.ts +1 -0
- package/dist/weather/fireSpreadPose.js +1 -0
- package/dist/weather/weatherGeometry.d.ts +1 -0
- package/dist/weather/weatherGeometry.js +1 -0
- package/dist/weather/weatherMath.d.ts +3 -0
- package/dist/weather/weatherMath.js +3 -0
- package/dist/weather/weatherUniforms.d.ts +3 -0
- package/dist/weather/weatherUniforms.js +3 -0
- package/dist/world/RetainedVfx.d.ts +10 -0
- package/dist/world/RetainedVfx.js +136 -0
- package/dist/world/WorldHud.d.ts +52 -3
- package/dist/world/WorldHud.js +94 -9
- package/dist/world/WorldScene.d.ts +21 -0
- package/dist/world/WorldScene.js +159 -0
- package/dist/world/WorldVfx.d.ts +9 -0
- package/dist/world/WorldVfx.js +184 -0
- package/dist/world/entityPose.d.ts +2 -0
- package/dist/world/entityPose.js +2 -0
- package/dist/world/floatTextStyle.d.ts +1 -0
- package/dist/world/floatTextStyle.js +1 -0
- package/dist/world/telegraphPulse.d.ts +1 -0
- package/dist/world/telegraphPulse.js +1 -0
- package/dist/world/worldBarSamples.d.ts +26 -1
- package/dist/world/worldBarSamples.js +45 -1
- package/dist/worldSky.d.ts +5 -0
- package/dist/worldSky.js +11 -0
- package/dist/worldSync.d.ts +11 -0
- package/dist/worldSync.js +20 -0
- package/package.json +6 -6
- package/skills/jgengine/SKILL.md +51 -0
- package/skills/jgengine/api.md +434 -0
- package/skills/jgengine/capabilities.md +29 -0
- package/skills/jgengine/reference-cartridge.md +72 -0
- package/skills/jgengine-ui/SKILL.md +36 -0
- package/skills/jgengine-ui/api.md +1852 -0
- package/skills/jgengine-ui/capabilities.md +142 -0
- package/skills/jgengine-ui/reference.md +500 -0
- package/skills/jgengine-ui/references/visual-scorecard.md +73 -0
- package/skills/jgengine-world/SKILL.md +38 -0
- package/skills/jgengine-world/api.md +2652 -0
- package/skills/jgengine-world/capabilities.md +370 -0
- package/skills/jgengine-world/reference.md +222 -0
- package/llms.txt +0 -2643
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { isServerAuthoritative } from "@jgengine/core/runtime/adapter";
|
|
3
|
+
import { attachWorldSync } from "./worldSync.js";
|
|
4
|
+
/** Joins the multiplayer server for the live context and wires presence, feed relay, and chat sync until teardown. */
|
|
5
|
+
export function useShellMultiplayerSync(ctx, multiplayer, playable, serverIdRef, setRemotePlayers) {
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (ctx === null || multiplayer === null)
|
|
8
|
+
return;
|
|
9
|
+
let disposed = false;
|
|
10
|
+
const cleanups = [];
|
|
11
|
+
void multiplayer.backend.transport
|
|
12
|
+
.joinServer({ gameId: multiplayer.gameId })
|
|
13
|
+
.then((joined) => {
|
|
14
|
+
if (disposed) {
|
|
15
|
+
void multiplayer.backend.transport.leaveServer({ serverId: joined.serverId });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
serverIdRef.current = joined.serverId;
|
|
19
|
+
if (isServerAuthoritative(playable.game.multiplayer) && multiplayer.backend.feeds !== undefined) {
|
|
20
|
+
cleanups.push(attachWorldSync(multiplayer.backend.feeds, joined.serverId, ctx));
|
|
21
|
+
}
|
|
22
|
+
cleanups.push(multiplayer.backend.presenceSync.subscribe(joined.serverId, (rows) => {
|
|
23
|
+
setRemotePlayers(rows.filter((row) => row.userId !== multiplayer.userId));
|
|
24
|
+
}));
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
let injecting = false;
|
|
27
|
+
for (const action of multiplayer.feedActions) {
|
|
28
|
+
cleanups.push(ctx.game.feed.subscribe(action, (entry) => {
|
|
29
|
+
if (injecting)
|
|
30
|
+
return;
|
|
31
|
+
void multiplayer.backend
|
|
32
|
+
.pushFeedEntry({
|
|
33
|
+
serverId: joined.serverId,
|
|
34
|
+
action,
|
|
35
|
+
entry: { at: entry.at, data: entry.data, from: multiplayer.userId },
|
|
36
|
+
})
|
|
37
|
+
.catch(() => undefined);
|
|
38
|
+
}));
|
|
39
|
+
const remoteUnsub = multiplayer.backend.feeds?.subscribeFeed({ serverId: joined.serverId, action }, (view) => {
|
|
40
|
+
for (const raw of view.entries) {
|
|
41
|
+
const remote = raw;
|
|
42
|
+
if (typeof remote.from !== "string" || remote.from === multiplayer.userId)
|
|
43
|
+
continue;
|
|
44
|
+
const key = `${action}|${remote.from}|${remote.at ?? 0}`;
|
|
45
|
+
if (seen.has(key))
|
|
46
|
+
continue;
|
|
47
|
+
seen.add(key);
|
|
48
|
+
injecting = true;
|
|
49
|
+
ctx.game.feed.push(action, remote.data);
|
|
50
|
+
injecting = false;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
if (remoteUnsub !== undefined)
|
|
54
|
+
cleanups.push(remoteUnsub);
|
|
55
|
+
}
|
|
56
|
+
const chatSync = multiplayer.backend.chatSyncFor?.(joined.serverId);
|
|
57
|
+
if (chatSync !== undefined && ctx.game.chat !== undefined) {
|
|
58
|
+
const chat = ctx.game.chat;
|
|
59
|
+
const globalChannelIds = new Set(chat
|
|
60
|
+
.channels()
|
|
61
|
+
.filter((channel) => channel.kind === "global")
|
|
62
|
+
.map((channel) => channel.id));
|
|
63
|
+
const seenRemoteChat = new Set();
|
|
64
|
+
cleanups.push(ctx.game.events.subscribe("chat.message", (event) => {
|
|
65
|
+
if (event.fromUserId !== multiplayer.userId)
|
|
66
|
+
return;
|
|
67
|
+
if (!globalChannelIds.has(event.channelId))
|
|
68
|
+
return;
|
|
69
|
+
void chatSync.send(event.channelId, event.body).catch(() => undefined);
|
|
70
|
+
}));
|
|
71
|
+
for (const channelId of globalChannelIds) {
|
|
72
|
+
cleanups.push(chatSync.subscribe(channelId, (messages) => {
|
|
73
|
+
for (const message of messages) {
|
|
74
|
+
if (message.fromUserId === multiplayer.userId)
|
|
75
|
+
continue;
|
|
76
|
+
if (seenRemoteChat.has(message.id))
|
|
77
|
+
continue;
|
|
78
|
+
seenRemoteChat.add(message.id);
|
|
79
|
+
chat.send(message.fromUserId, message.channelId, message.body);
|
|
80
|
+
}
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
.catch(() => undefined);
|
|
86
|
+
return () => {
|
|
87
|
+
disposed = true;
|
|
88
|
+
for (const cleanup of cleanups)
|
|
89
|
+
cleanup();
|
|
90
|
+
const serverId = serverIdRef.current;
|
|
91
|
+
serverIdRef.current = null;
|
|
92
|
+
setRemotePlayers([]);
|
|
93
|
+
if (serverId !== null) {
|
|
94
|
+
void multiplayer.backend.transport.leaveServer({ serverId }).catch(() => undefined);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}, [ctx, multiplayer, playable]);
|
|
98
|
+
}
|
|
@@ -4,7 +4,7 @@ import { useThree, useFrame } from "@react-three/fiber";
|
|
|
4
4
|
import * as THREE from "three";
|
|
5
5
|
import { useGameContext } from "@jgengine/react/provider";
|
|
6
6
|
import { createVisibilitySystem } from "@jgengine/core/visibility/visibilitySystem";
|
|
7
|
-
import { CAMERA_POST_FRAME_PRIORITY } from "../camera/
|
|
7
|
+
import { CAMERA_POST_FRAME_PRIORITY } from "../camera/cameraRigs.js";
|
|
8
8
|
const ALWAYS_VISIBLE = () => true;
|
|
9
9
|
const alwaysVisibleRef = { current: ALWAYS_VISIBLE };
|
|
10
10
|
const CullingContext = createContext(null);
|
|
@@ -96,7 +96,9 @@ export declare const OCEAN_QUALITY_PRESETS: Record<OceanQualityPreset, {
|
|
|
96
96
|
resolution: number;
|
|
97
97
|
}>;
|
|
98
98
|
export declare const DEFAULT_OCEAN_CONFIG: ResolvedOceanConfig;
|
|
99
|
+
/** @internal */
|
|
99
100
|
export declare function createOceanConfig(patch?: OceanConfig): ResolvedOceanConfig;
|
|
101
|
+
/** @internal */
|
|
100
102
|
export declare function buildOceanWaveUniforms(config: ResolvedOceanConfig): {
|
|
101
103
|
directions: THREE.Vector2[];
|
|
102
104
|
params: THREE.Vector4[];
|
|
@@ -87,6 +87,7 @@ function resolveWave(wave, fallback, config, customWaves) {
|
|
|
87
87
|
steepness: clamp(finiteOr(wave?.steepness, fallback.steepness) * config.steepness, 0, 1.2),
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
|
+
/** @internal */
|
|
90
91
|
export function createOceanConfig(patch = {}) {
|
|
91
92
|
const quality = patch.quality ?? DEFAULT_OCEAN_CONFIG.quality;
|
|
92
93
|
const preset = OCEAN_QUALITY_PRESETS[quality];
|
|
@@ -126,6 +127,7 @@ export function createOceanConfig(patch = {}) {
|
|
|
126
127
|
const waves = Array.from({ length: MAX_OCEAN_WAVES }, (_, index) => resolveWave(inputWaves[index], defaultWaves[index], withoutWaves, customWaves));
|
|
127
128
|
return { ...withoutWaves, waves };
|
|
128
129
|
}
|
|
130
|
+
/** @internal */
|
|
129
131
|
export function buildOceanWaveUniforms(config) {
|
|
130
132
|
const directions = [];
|
|
131
133
|
const params = [];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
2
|
import { createWeatherSeedAttributes } from "./weatherMath.js";
|
|
3
|
+
/** @internal */
|
|
3
4
|
export function createWeatherQuadGeometry(maxCount, seed) {
|
|
4
5
|
const count = Math.max(0, Math.floor(maxCount));
|
|
5
6
|
const geometry = new THREE.InstancedBufferGeometry();
|
|
@@ -2,10 +2,13 @@ export declare const DEFAULT_RAIN_COUNT = 2000;
|
|
|
2
2
|
export declare const DEFAULT_SNOW_COUNT = 1500;
|
|
3
3
|
export declare const DEFAULT_RAIN_DENSITY = 0.45;
|
|
4
4
|
export declare const DEFAULT_SNOW_DENSITY = 0.5;
|
|
5
|
+
/** @internal */
|
|
5
6
|
export declare function clampWeatherRatio(value: number): number;
|
|
7
|
+
/** @internal */
|
|
6
8
|
export declare function resolveWeatherInstanceCount(maxCount: number, density: number, budget?: number): number;
|
|
7
9
|
export interface WeatherSeedAttributes {
|
|
8
10
|
spawn: Float32Array;
|
|
9
11
|
drift: Float32Array;
|
|
10
12
|
}
|
|
13
|
+
/** @internal */
|
|
11
14
|
export declare function createWeatherSeedAttributes(maxCount: number, seed: number): WeatherSeedAttributes;
|
|
@@ -2,6 +2,7 @@ export const DEFAULT_RAIN_COUNT = 2000;
|
|
|
2
2
|
export const DEFAULT_SNOW_COUNT = 1500;
|
|
3
3
|
export const DEFAULT_RAIN_DENSITY = 0.45;
|
|
4
4
|
export const DEFAULT_SNOW_DENSITY = 0.5;
|
|
5
|
+
/** @internal */
|
|
5
6
|
export function clampWeatherRatio(value) {
|
|
6
7
|
if (!Number.isFinite(value))
|
|
7
8
|
return 0;
|
|
@@ -11,11 +12,13 @@ export function clampWeatherRatio(value) {
|
|
|
11
12
|
return 1;
|
|
12
13
|
return value;
|
|
13
14
|
}
|
|
15
|
+
/** @internal */
|
|
14
16
|
export function resolveWeatherInstanceCount(maxCount, density, budget) {
|
|
15
17
|
const safeMax = Math.max(0, Math.floor(maxCount));
|
|
16
18
|
const capped = budget === undefined ? safeMax : Math.min(safeMax, Math.max(0, Math.floor(budget)));
|
|
17
19
|
return Math.floor(capped * clampWeatherRatio(density));
|
|
18
20
|
}
|
|
21
|
+
/** @internal */
|
|
19
22
|
export function createWeatherSeedAttributes(maxCount, seed) {
|
|
20
23
|
const count = Math.max(0, Math.floor(maxCount));
|
|
21
24
|
const spawn = new Float32Array(count * 3);
|
|
@@ -11,8 +11,11 @@ export interface WeatherUniformOptions {
|
|
|
11
11
|
lightning?: number;
|
|
12
12
|
timeScale?: number;
|
|
13
13
|
}
|
|
14
|
+
/** @internal */
|
|
14
15
|
export declare function createWeatherUniformSet(options?: WeatherUniformOptions): WeatherUniformSet;
|
|
16
|
+
/** @internal */
|
|
15
17
|
export declare function WeatherUniformProvider({ children, ...options }: WeatherUniformOptions & {
|
|
16
18
|
children: ReactNode;
|
|
17
19
|
}): import("react").JSX.Element;
|
|
20
|
+
/** @internal */
|
|
18
21
|
export declare function useWeatherUniformSet(options?: WeatherUniformOptions): WeatherUniformSet;
|
|
@@ -3,6 +3,7 @@ import { createContext, useContext, useMemo } from "react";
|
|
|
3
3
|
import { useFrame } from "@react-three/fiber";
|
|
4
4
|
import * as THREE from "three";
|
|
5
5
|
const WeatherUniformContext = createContext(null);
|
|
6
|
+
/** @internal */
|
|
6
7
|
export function createWeatherUniformSet(options = {}) {
|
|
7
8
|
const wind = options.wind ?? [0, 0, 0];
|
|
8
9
|
return {
|
|
@@ -21,11 +22,13 @@ function useUniformTicker(uniforms, options, enabled = true) {
|
|
|
21
22
|
uniforms.lightning.value = options.lightning ?? 0;
|
|
22
23
|
});
|
|
23
24
|
}
|
|
25
|
+
/** @internal */
|
|
24
26
|
export function WeatherUniformProvider({ children, ...options }) {
|
|
25
27
|
const uniforms = useMemo(() => createWeatherUniformSet(options), []);
|
|
26
28
|
useUniformTicker(uniforms, options);
|
|
27
29
|
return _jsx(WeatherUniformContext.Provider, { value: uniforms, children: children });
|
|
28
30
|
}
|
|
31
|
+
/** @internal */
|
|
29
32
|
export function useWeatherUniformSet(options = {}) {
|
|
30
33
|
const shared = useContext(WeatherUniformContext);
|
|
31
34
|
const local = useMemo(() => createWeatherUniformSet(options), []);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders retained VFX instances (`combat.vfxInstance` ops from {@link GameContext}'s `scene.entity.vfxInstance`
|
|
3
|
+
* store) as long-lived beams whose endpoints follow live entity poses. Auto-mounted by the player shell alongside
|
|
4
|
+
* the one-shot {@link WorldSpellVfx} overlay. `upsert`/`update` mutate render resources imperatively (no per-frame
|
|
5
|
+
* React allocation); only create/stop change the mounted set, and `stop` fades out over its requested duration.
|
|
6
|
+
* Beam is the first shipped retained renderer; other kinds are ignored here for a future renderer to claim.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export declare function RetainedVfx(): import("react").JSX.Element;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useFrame } from "@react-three/fiber";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import * as THREE from "three";
|
|
5
|
+
import { useGameContext } from "@jgengine/react/provider";
|
|
6
|
+
const UP = new THREE.Vector3(0, 1, 0);
|
|
7
|
+
/**
|
|
8
|
+
* Resolve an endpoint into `out`. A world point is used literally; an entity id resolves to its live pose lifted
|
|
9
|
+
* by `lift` (so a beam meets an entity near chest height, not its feet). Returns false when an entity ref is
|
|
10
|
+
* missing, letting the caller hide the effect until the target exists again.
|
|
11
|
+
*/
|
|
12
|
+
function resolveRef(ctx, ref, lift, out) {
|
|
13
|
+
if (ref === undefined)
|
|
14
|
+
return false;
|
|
15
|
+
if (typeof ref === "string") {
|
|
16
|
+
const entity = ctx.scene.entity.get(ref);
|
|
17
|
+
if (entity === null)
|
|
18
|
+
return false;
|
|
19
|
+
out.set(entity.position[0], entity.position[1] + lift, entity.position[2]);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
out.set(ref[0], ref[1], ref[2]);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* One retained beam mesh. It reads its authoritative state from the shared live map every frame and resolves
|
|
27
|
+
* `from`/`to` refs against the entity store, so an endpoint bound to a moving entity follows it with no per-frame
|
|
28
|
+
* command traffic or React re-render — parameter updates mutate the render resources imperatively.
|
|
29
|
+
*/
|
|
30
|
+
function RetainedBeam({ id, live, onFadeComplete, }) {
|
|
31
|
+
const ctx = useGameContext();
|
|
32
|
+
const meshRef = useRef(null);
|
|
33
|
+
const matRef = useRef(null);
|
|
34
|
+
const from = useRef(new THREE.Vector3());
|
|
35
|
+
const to = useRef(new THREE.Vector3());
|
|
36
|
+
const delta = useRef(new THREE.Vector3());
|
|
37
|
+
const quat = useRef(new THREE.Quaternion());
|
|
38
|
+
useFrame(() => {
|
|
39
|
+
const entry = live.get(id);
|
|
40
|
+
const mesh = meshRef.current;
|
|
41
|
+
const mat = matRef.current;
|
|
42
|
+
if (entry === undefined || mesh === null || mat === null)
|
|
43
|
+
return;
|
|
44
|
+
const lift = entry.state.params?.lift ?? 1;
|
|
45
|
+
const hasFrom = resolveRef(ctx, entry.state.from, lift, from.current);
|
|
46
|
+
const hasTo = resolveRef(ctx, entry.state.to, lift, to.current);
|
|
47
|
+
if (!hasFrom || !hasTo) {
|
|
48
|
+
mesh.visible = false;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
delta.current.subVectors(to.current, from.current);
|
|
52
|
+
const length = Math.max(0.001, delta.current.length());
|
|
53
|
+
mesh.visible = true;
|
|
54
|
+
mesh.position.set((from.current.x + to.current.x) / 2, (from.current.y + to.current.y) / 2, (from.current.z + to.current.z) / 2);
|
|
55
|
+
quat.current.setFromUnitVectors(UP, delta.current.normalize());
|
|
56
|
+
mesh.quaternion.copy(quat.current);
|
|
57
|
+
const thickness = entry.state.radius ?? 0.12;
|
|
58
|
+
mesh.scale.set(thickness, length, thickness);
|
|
59
|
+
mat.color.set(entry.state.color);
|
|
60
|
+
const baseOpacity = entry.state.params?.opacity ?? 0.85;
|
|
61
|
+
if (entry.fade === null) {
|
|
62
|
+
mat.opacity = baseOpacity;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const p = (performance.now() - entry.fade.startedAt) / Math.max(1, entry.fade.ms);
|
|
66
|
+
if (p >= 1) {
|
|
67
|
+
onFadeComplete(id);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
mat.opacity = baseOpacity * (1 - p);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
return (_jsxs("mesh", { ref: meshRef, renderOrder: 998, visible: false, children: [_jsx("cylinderGeometry", { args: [1, 1, 1, 8, 1, true] }), _jsx("meshBasicMaterial", { ref: matRef, transparent: true, side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending, toneMapped: false, opacity: 0 })] }));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Renders retained VFX instances (`combat.vfxInstance` ops from {@link GameContext}'s `scene.entity.vfxInstance`
|
|
77
|
+
* store) as long-lived beams whose endpoints follow live entity poses. Auto-mounted by the player shell alongside
|
|
78
|
+
* the one-shot {@link WorldSpellVfx} overlay. `upsert`/`update` mutate render resources imperatively (no per-frame
|
|
79
|
+
* React allocation); only create/stop change the mounted set, and `stop` fades out over its requested duration.
|
|
80
|
+
* Beam is the first shipped retained renderer; other kinds are ignored here for a future renderer to claim.
|
|
81
|
+
*
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
84
|
+
export function RetainedVfx() {
|
|
85
|
+
const ctx = useGameContext();
|
|
86
|
+
const live = useRef(new Map()).current;
|
|
87
|
+
const [ids, setIds] = useState([]);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
// Seed from any instances created before this overlay mounted.
|
|
90
|
+
for (const state of ctx.scene.entity.vfxInstance.list()) {
|
|
91
|
+
if (state.kind !== "beam")
|
|
92
|
+
continue;
|
|
93
|
+
live.set(state.id, { state, fade: null });
|
|
94
|
+
}
|
|
95
|
+
if (live.size > 0)
|
|
96
|
+
setIds([...live.keys()]);
|
|
97
|
+
const off = ctx.game.events.on("combat.vfxInstance", (op) => {
|
|
98
|
+
if (op.op === "stop") {
|
|
99
|
+
const entry = live.get(op.id);
|
|
100
|
+
if (entry === undefined)
|
|
101
|
+
return;
|
|
102
|
+
if (op.fadeMs !== undefined && op.fadeMs > 0) {
|
|
103
|
+
entry.fade = { ms: op.fadeMs, startedAt: performance.now() };
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
live.delete(op.id);
|
|
107
|
+
setIds([...live.keys()]);
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const state = op.instance;
|
|
112
|
+
if (state === undefined || state.kind !== "beam")
|
|
113
|
+
return;
|
|
114
|
+
const existing = live.get(op.id);
|
|
115
|
+
if (existing === undefined) {
|
|
116
|
+
live.set(op.id, { state, fade: null });
|
|
117
|
+
setIds([...live.keys()]);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Update: mutate state in place; RetainedBeam reads it next frame (no React re-render).
|
|
121
|
+
existing.state = state;
|
|
122
|
+
existing.fade = null;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
return () => {
|
|
126
|
+
off();
|
|
127
|
+
live.clear();
|
|
128
|
+
setIds([]);
|
|
129
|
+
};
|
|
130
|
+
}, [ctx, live]);
|
|
131
|
+
function removeId(id) {
|
|
132
|
+
live.delete(id);
|
|
133
|
+
setIds((current) => current.filter((entry) => entry !== id));
|
|
134
|
+
}
|
|
135
|
+
return (_jsx(_Fragment, { children: ids.map((id) => (_jsx(RetainedBeam, { id: id, live: live, onFadeComplete: removeId }, id))) }));
|
|
136
|
+
}
|
package/dist/world/WorldHud.d.ts
CHANGED
|
@@ -1,14 +1,63 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
1
2
|
import type { SceneEntity } from "@jgengine/core/scene/entityStore";
|
|
2
3
|
import type { CatalogEntityRole } from "@jgengine/core/runtime/gameContext";
|
|
3
|
-
|
|
4
|
-
export {
|
|
4
|
+
import { type NameplateSample } from "./worldBarSamples.js";
|
|
5
|
+
export type { NameplateSample, WorldBarSample } from "./worldBarSamples.js";
|
|
6
|
+
export { collectNameplateSamples, collectWorldBarSamples, paintWorldBarSamples } from "./worldBarSamples.js";
|
|
5
7
|
export { telegraphPulseOpacity } from "./telegraphPulse.js";
|
|
6
|
-
export declare function WorldEntityBars({ statId, height, roles, resolveRole, }: {
|
|
8
|
+
export declare function WorldEntityBars({ statId, height, roles, resolveRole, maxDistance, }: {
|
|
7
9
|
statId: string;
|
|
8
10
|
height?: number;
|
|
9
11
|
roles?: readonly CatalogEntityRole[];
|
|
10
12
|
resolveRole?: (entity: SceneEntity) => CatalogEntityRole | undefined;
|
|
13
|
+
/** Hide bars for entities farther than this from the player (world units). Default 60. */
|
|
14
|
+
maxDistance?: number;
|
|
11
15
|
}): import("react").JSX.Element;
|
|
16
|
+
/** Props for `WorldNameplates` — entity filter, refresh rate, and headless className/render hooks. */
|
|
17
|
+
export interface WorldNameplatesProps {
|
|
18
|
+
statId?: string;
|
|
19
|
+
height?: number;
|
|
20
|
+
roles?: readonly CatalogEntityRole[];
|
|
21
|
+
resolveRole?: (entity: SceneEntity) => CatalogEntityRole | undefined;
|
|
22
|
+
/** Hide nameplates for entities farther than this from the player (world units). Default 40. */
|
|
23
|
+
maxDistance?: number;
|
|
24
|
+
/** Minimum ms between position/health refreshes — trades smoothness for fewer re-renders at scale. Default 120. */
|
|
25
|
+
tickMs?: number;
|
|
26
|
+
className?: string;
|
|
27
|
+
nameplateClassName?: string;
|
|
28
|
+
nameClassName?: string;
|
|
29
|
+
barClassName?: string;
|
|
30
|
+
fillClassName?: string;
|
|
31
|
+
/** Full override for one nameplate's markup; receives the raw sample (name, percent, screen x/y). */
|
|
32
|
+
renderNameplate?: (sample: NameplateSample) => ReactNode;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Billboarded name + 78×6px HP bar over every nearby non-local entity that
|
|
36
|
+
* passes `roles`/`maxDistance` — headless (className/data-* slots on every
|
|
37
|
+
* part, `renderNameplate` for a full swap), turned on declaratively via
|
|
38
|
+
* `defineGame({ nameplates })` rather than mounted by hand.
|
|
39
|
+
*
|
|
40
|
+
* @capability nameplates billboarded name + HP bar over nearby entities
|
|
41
|
+
*/
|
|
42
|
+
export declare function WorldNameplates({ statId, height, roles, resolveRole, maxDistance, tickMs, className, nameplateClassName, nameClassName, barClassName, fillClassName, renderNameplate, }: WorldNameplatesProps): import("react").JSX.Element;
|
|
43
|
+
/** Props for {@link WorldObjectHighlights}. */
|
|
44
|
+
export interface WorldObjectHighlightsProps {
|
|
45
|
+
/** Ring tint. Default a construction-select amber. */
|
|
46
|
+
color?: string;
|
|
47
|
+
/** Fixed ring radius; omitted derives one from the object's catalog `halfExtents` (falls back to 1.2). */
|
|
48
|
+
radius?: number;
|
|
49
|
+
/** Ground offset so the ring doesn't z-fight the object's base. Default 0.05. */
|
|
50
|
+
y?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Ground ring over every `ctx.scene.object.selection`-ed placed object — the object-layer counterpart
|
|
54
|
+
* to `WorldEntityBars`/`WorldNameplates`. Mount it once in the game's scene (headless: no defaults are
|
|
55
|
+
* imposed beyond a visible ring) instead of hand-rolling a selection highlight through `WorldOverlay`
|
|
56
|
+
* against external state.
|
|
57
|
+
*
|
|
58
|
+
* @capability world-object-highlights ground-ring highlight over every selected placed object
|
|
59
|
+
*/
|
|
60
|
+
export declare function WorldObjectHighlights({ color, radius, y }: WorldObjectHighlightsProps): import("react").JSX.Element;
|
|
12
61
|
export declare function WorldFloatText({ height, lifeMs }: {
|
|
13
62
|
height?: number;
|
|
14
63
|
lifeMs?: number;
|
package/dist/world/WorldHud.js
CHANGED
|
@@ -5,16 +5,17 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
|
|
5
5
|
import * as THREE from "three";
|
|
6
6
|
import { useGameContext } from "@jgengine/react/provider";
|
|
7
7
|
import { useCameraShake } from "../camera/shakeChannel.js";
|
|
8
|
+
import { CALIBRATED_TRAUMA_SHAKE_DECAY_PER_SECOND } from "../camera/rigMath.js";
|
|
9
|
+
import { readFirstPersonMuzzle } from "../camera/GameFirstPersonCamera.js";
|
|
8
10
|
import { resolveFloatTextStyle } from "./floatTextStyle.js";
|
|
9
|
-
import { collectWorldBarSamples, paintWorldBarSamples, } from "./worldBarSamples.js";
|
|
11
|
+
import { collectNameplateSamples, collectWorldBarSamples, paintWorldBarSamples, } from "./worldBarSamples.js";
|
|
10
12
|
import { telegraphPulseOpacity } from "./telegraphPulse.js";
|
|
11
|
-
export { collectWorldBarSamples, paintWorldBarSamples } from "./worldBarSamples.js";
|
|
13
|
+
export { collectNameplateSamples, collectWorldBarSamples, paintWorldBarSamples } from "./worldBarSamples.js";
|
|
12
14
|
export { telegraphPulseOpacity } from "./telegraphPulse.js";
|
|
13
|
-
const MUZZLE_HEIGHT = 1.4;
|
|
14
15
|
function pinOverlayToViewport(_el, _camera, size) {
|
|
15
16
|
return [size.width / 2, size.height / 2];
|
|
16
17
|
}
|
|
17
|
-
export function WorldEntityBars({ statId, height = 2.2, roles, resolveRole, }) {
|
|
18
|
+
export function WorldEntityBars({ statId, height = 2.2, roles, resolveRole, maxDistance = 60, }) {
|
|
18
19
|
const ctx = useGameContext();
|
|
19
20
|
const camera = useThree((state) => state.camera);
|
|
20
21
|
const size = useThree((state) => state.size);
|
|
@@ -36,11 +37,91 @@ export function WorldEntityBars({ statId, height = 2.2, roles, resolveRole, }) {
|
|
|
36
37
|
canvas.height = pixelH;
|
|
37
38
|
}
|
|
38
39
|
camera.updateMatrixWorld();
|
|
39
|
-
collectWorldBarSamples(ctx, statId, height, roles, resolveRole, camera, { width: cssW, height: cssH }, samplesRef.current, projectRef.current);
|
|
40
|
+
collectWorldBarSamples(ctx, statId, height, roles, resolveRole, camera, { width: cssW, height: cssH }, samplesRef.current, projectRef.current, maxDistance);
|
|
40
41
|
paintWorldBarSamples(canvas, samplesRef.current, dpr);
|
|
41
42
|
});
|
|
42
43
|
return (_jsx(Html, { fullscreen: true, calculatePosition: pinOverlayToViewport, zIndexRange: [20, 0], style: { pointerEvents: "none" }, children: _jsx("canvas", { ref: canvasRef, style: { width: "100%", height: "100%", display: "block", pointerEvents: "none" } }) }));
|
|
43
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Billboarded name + 78×6px HP bar over every nearby non-local entity that
|
|
47
|
+
* passes `roles`/`maxDistance` — headless (className/data-* slots on every
|
|
48
|
+
* part, `renderNameplate` for a full swap), turned on declaratively via
|
|
49
|
+
* `defineGame({ nameplates })` rather than mounted by hand.
|
|
50
|
+
*
|
|
51
|
+
* @capability nameplates billboarded name + HP bar over nearby entities
|
|
52
|
+
*/
|
|
53
|
+
export function WorldNameplates({ statId = "health", height = 2.3, roles, resolveRole, maxDistance = 40, tickMs = 120, className, nameplateClassName, nameClassName, barClassName, fillClassName, renderNameplate, }) {
|
|
54
|
+
const ctx = useGameContext();
|
|
55
|
+
const camera = useThree((state) => state.camera);
|
|
56
|
+
const size = useThree((state) => state.size);
|
|
57
|
+
const [samples, setSamples] = useState([]);
|
|
58
|
+
const samplesRef = useRef([]);
|
|
59
|
+
const projectRef = useRef(new THREE.Vector3());
|
|
60
|
+
const lastTickRef = useRef(0);
|
|
61
|
+
useFrame((state) => {
|
|
62
|
+
const nowMs = state.clock.elapsedTime * 1000;
|
|
63
|
+
if (nowMs - lastTickRef.current < tickMs)
|
|
64
|
+
return;
|
|
65
|
+
lastTickRef.current = nowMs;
|
|
66
|
+
camera.updateMatrixWorld();
|
|
67
|
+
collectNameplateSamples(ctx, statId, height, roles, resolveRole, camera, { width: size.width, height: size.height }, samplesRef.current, projectRef.current, maxDistance);
|
|
68
|
+
setSamples([...samplesRef.current]);
|
|
69
|
+
});
|
|
70
|
+
return (_jsx(Html, { fullscreen: true, calculatePosition: pinOverlayToViewport, zIndexRange: [19, 0], style: { pointerEvents: "none" }, children: _jsx("div", { className: className, "data-nameplates": true, children: samples.map((sample) => renderNameplate !== undefined ? (_jsx("div", { style: { position: "absolute", left: sample.x, top: sample.y }, children: renderNameplate(sample) }, sample.id)) : (_jsxs("div", { className: nameplateClassName, "data-nameplate": sample.id, style: {
|
|
71
|
+
position: "absolute",
|
|
72
|
+
left: sample.x,
|
|
73
|
+
top: sample.y,
|
|
74
|
+
transform: "translate(-50%, -100%)",
|
|
75
|
+
display: "flex",
|
|
76
|
+
flexDirection: "column",
|
|
77
|
+
alignItems: "center",
|
|
78
|
+
gap: 2,
|
|
79
|
+
fontFamily: "ui-sans-serif, system-ui, sans-serif",
|
|
80
|
+
}, children: [_jsx("span", { className: nameClassName, "data-nameplate-name": true, style: {
|
|
81
|
+
fontSize: 12,
|
|
82
|
+
fontWeight: 700,
|
|
83
|
+
color: "#f1f5f9",
|
|
84
|
+
textShadow: "0 1px 3px rgba(0,0,0,0.9)",
|
|
85
|
+
whiteSpace: "nowrap",
|
|
86
|
+
}, children: sample.name }), sample.percent !== null ? (_jsx("span", { className: barClassName, "data-nameplate-bar": true, style: {
|
|
87
|
+
display: "block",
|
|
88
|
+
width: 78,
|
|
89
|
+
height: 6,
|
|
90
|
+
borderRadius: 2,
|
|
91
|
+
background: "rgba(0,0,0,0.65)",
|
|
92
|
+
border: "1px solid rgba(0,0,0,0.8)",
|
|
93
|
+
overflow: "hidden",
|
|
94
|
+
}, children: _jsx("span", { className: fillClassName, "data-nameplate-fill": true, style: {
|
|
95
|
+
display: "block",
|
|
96
|
+
width: `${sample.percent * 100}%`,
|
|
97
|
+
height: "100%",
|
|
98
|
+
background: sample.percent > 0.5 ? "#22c55e" : sample.percent > 0.25 ? "#f59e0b" : "#ef4444",
|
|
99
|
+
} }) })) : null] }, sample.id))) }) }));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Ground ring over every `ctx.scene.object.selection`-ed placed object — the object-layer counterpart
|
|
103
|
+
* to `WorldEntityBars`/`WorldNameplates`. Mount it once in the game's scene (headless: no defaults are
|
|
104
|
+
* imposed beyond a visible ring) instead of hand-rolling a selection highlight through `WorldOverlay`
|
|
105
|
+
* against external state.
|
|
106
|
+
*
|
|
107
|
+
* @capability world-object-highlights ground-ring highlight over every selected placed object
|
|
108
|
+
*/
|
|
109
|
+
export function WorldObjectHighlights({ color = "#facc15", radius, y = 0.05 }) {
|
|
110
|
+
const ctx = useGameContext();
|
|
111
|
+
const [ids, setIds] = useState(() => ctx.scene.object.selection.list());
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
setIds(ctx.scene.object.selection.list());
|
|
114
|
+
return ctx.subscribe(() => setIds(ctx.scene.object.selection.list()));
|
|
115
|
+
}, [ctx]);
|
|
116
|
+
return (_jsx(_Fragment, { children: ids.map((id) => {
|
|
117
|
+
const object = ctx.scene.object.get(id);
|
|
118
|
+
if (object === null)
|
|
119
|
+
return null;
|
|
120
|
+
const halfExtents = ctx.scene.object.catalog(id)?.halfExtents;
|
|
121
|
+
const ringRadius = radius ?? (halfExtents === undefined ? 1.2 : Math.max(halfExtents[0], halfExtents[2]) * 1.35);
|
|
122
|
+
return (_jsxs("mesh", { position: [object.position[0], object.position[1] + y, object.position[2]], rotation: [-Math.PI / 2, 0, 0], renderOrder: 998, children: [_jsx("ringGeometry", { args: [ringRadius * 0.82, ringRadius, 40] }), _jsx("meshBasicMaterial", { color: color, transparent: true, opacity: 0.85, side: THREE.DoubleSide, depthWrite: false, toneMapped: false })] }, id));
|
|
123
|
+
}) }));
|
|
124
|
+
}
|
|
44
125
|
function FloatTextItem({ event, lifeMs }) {
|
|
45
126
|
const [shown, setShown] = useState(false);
|
|
46
127
|
const style = useMemo(() => resolveFloatTextStyle(event), [event]);
|
|
@@ -142,6 +223,10 @@ export function CombatCameraShake() {
|
|
|
142
223
|
const shake = useCameraShake();
|
|
143
224
|
useEffect(() => {
|
|
144
225
|
return ctx.game.events.on("combat.hitReaction", (event) => {
|
|
226
|
+
if (event.trauma !== undefined) {
|
|
227
|
+
shake.shake(event.trauma, CALIBRATED_TRAUMA_SHAKE_DECAY_PER_SECOND);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
145
230
|
if (event.shake === undefined)
|
|
146
231
|
return;
|
|
147
232
|
shake.shake(event.shake.amplitude, event.shake.decay);
|
|
@@ -157,14 +242,14 @@ export function ProjectileTracers({ lifeMs = 130 }) {
|
|
|
157
242
|
useEffect(() => {
|
|
158
243
|
const unsub = ctx.game.events.on("projectile.settled", (event) => {
|
|
159
244
|
const id = nextId.current++;
|
|
245
|
+
const start = new THREE.Vector3(event.origin[0], event.origin[1], event.origin[2]);
|
|
246
|
+
if (event.from === ctx.player.userId)
|
|
247
|
+
readFirstPersonMuzzle(start);
|
|
160
248
|
setTracers((current) => [
|
|
161
249
|
...current,
|
|
162
250
|
{
|
|
163
251
|
id,
|
|
164
|
-
points: [
|
|
165
|
-
new THREE.Vector3(event.origin[0], event.origin[1] + MUZZLE_HEIGHT, event.origin[2]),
|
|
166
|
-
new THREE.Vector3(event.at[0], event.at[1], event.at[2]),
|
|
167
|
-
],
|
|
252
|
+
points: [start, new THREE.Vector3(event.at[0], event.at[1], event.at[2])],
|
|
168
253
|
},
|
|
169
254
|
]);
|
|
170
255
|
const handle = setTimeout(() => {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ComponentType, type ReactNode } from "react";
|
|
2
|
+
import type { AssetCatalog } from "@jgengine/core/scene/assetCatalog";
|
|
3
|
+
import type { SceneEntity } from "@jgengine/core/scene/entityStore";
|
|
4
|
+
import { type SceneObject } from "@jgengine/core/scene/objectStore";
|
|
5
|
+
import type { EntitySpriteConfig, ModelConfig, ObjectStyle } from "@jgengine/core/game/playableGame";
|
|
6
|
+
import type { PresencePoseRow } from "@jgengine/core/runtime/transport";
|
|
7
|
+
export declare function WorldView({ entitySprites, entityModels, objectModels, objectStyles, environment, assets, renderEntity, renderObject, selectedIds, hideLocalActor, }: {
|
|
8
|
+
entitySprites: Record<string, EntitySpriteConfig> | undefined;
|
|
9
|
+
entityModels: Record<string, string | ModelConfig> | undefined;
|
|
10
|
+
objectModels: Record<string, string | ModelConfig> | undefined;
|
|
11
|
+
objectStyles: Record<string, ObjectStyle> | undefined;
|
|
12
|
+
environment: ComponentType | undefined;
|
|
13
|
+
assets: AssetCatalog;
|
|
14
|
+
renderEntity: ((entity: SceneEntity) => ReactNode) | undefined;
|
|
15
|
+
renderObject: ((object: SceneObject) => ReactNode) | undefined;
|
|
16
|
+
selectedIds: ReadonlySet<string>;
|
|
17
|
+
hideLocalActor: boolean;
|
|
18
|
+
}): import("react").JSX.Element;
|
|
19
|
+
export declare function RemotePlayers({ rows }: {
|
|
20
|
+
rows: PresencePoseRow[];
|
|
21
|
+
}): import("react").JSX.Element;
|