@jgengine/shell 0.10.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 +13 -2
- 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 +0 -12
- package/dist/GamePlayerShell.js +88 -1043
- package/dist/GameUiPreview.js +0 -2
- package/dist/audio/audioEngine.js +30 -8
- package/dist/audio/audioWire.d.ts +22 -0
- package/dist/audio/audioWire.js +19 -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 +9 -2
- package/dist/camera/GameFirstPersonCamera.js +8 -3
- package/dist/camera/GameInspectionCamera.d.ts +1 -1
- package/dist/camera/GameInspectionCamera.js +2 -2
- package/dist/camera/GameOrbitCamera.js +24 -9
- 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 +36 -6
- package/dist/camera/orbitCameraMath.js +56 -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 +3 -3
- package/dist/commandSink.d.ts +8 -3
- package/dist/commandSink.js +8 -3
- package/dist/defineGame.js +25 -6
- package/dist/devtools/ColPanel.d.ts +1 -0
- package/dist/devtools/ColPanel.js +14 -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 +8 -2
- package/dist/devtools/collisionDebugMath.js +24 -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 +23 -7
- package/dist/environment/Daylight.js +106 -13
- package/dist/environment/EnvironmentScene.js +32 -6
- 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 +16 -4
- package/dist/inputSink.js +57 -7
- package/dist/materialOverride.d.ts +10 -0
- package/dist/materialOverride.js +15 -3
- package/dist/multiplayer.js +8 -2
- package/dist/postfx/PostProcessing.js +47 -9
- package/dist/postfx/gradeShader.js +9 -1
- 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/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 +19 -2
- package/dist/terrain/terrainDetailMaterial.js +72 -5
- 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 +201 -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 +49 -2
- package/dist/world/WorldHud.js +87 -2
- 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 +25 -0
- package/dist/world/worldBarSamples.js +38 -0
- package/dist/worldSky.d.ts +5 -0
- package/dist/worldSky.js +11 -0
- package/dist/worldSync.d.ts +2 -1
- package/dist/worldSync.js +2 -1
- 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 -2761
|
@@ -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,7 +1,9 @@
|
|
|
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
8
|
export declare function WorldEntityBars({ statId, height, roles, resolveRole, maxDistance, }: {
|
|
7
9
|
statId: string;
|
|
@@ -11,6 +13,51 @@ export declare function WorldEntityBars({ statId, height, roles, resolveRole, ma
|
|
|
11
13
|
/** Hide bars for entities farther than this from the player (world units). Default 60. */
|
|
12
14
|
maxDistance?: number;
|
|
13
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;
|
|
14
61
|
export declare function WorldFloatText({ height, lifeMs }: {
|
|
15
62
|
height?: number;
|
|
16
63
|
lifeMs?: number;
|
package/dist/world/WorldHud.js
CHANGED
|
@@ -5,11 +5,12 @@ 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";
|
|
8
9
|
import { readFirstPersonMuzzle } from "../camera/GameFirstPersonCamera.js";
|
|
9
10
|
import { resolveFloatTextStyle } from "./floatTextStyle.js";
|
|
10
|
-
import { collectWorldBarSamples, paintWorldBarSamples, } from "./worldBarSamples.js";
|
|
11
|
+
import { collectNameplateSamples, collectWorldBarSamples, paintWorldBarSamples, } from "./worldBarSamples.js";
|
|
11
12
|
import { telegraphPulseOpacity } from "./telegraphPulse.js";
|
|
12
|
-
export { collectWorldBarSamples, paintWorldBarSamples } from "./worldBarSamples.js";
|
|
13
|
+
export { collectNameplateSamples, collectWorldBarSamples, paintWorldBarSamples } from "./worldBarSamples.js";
|
|
13
14
|
export { telegraphPulseOpacity } from "./telegraphPulse.js";
|
|
14
15
|
function pinOverlayToViewport(_el, _camera, size) {
|
|
15
16
|
return [size.width / 2, size.height / 2];
|
|
@@ -41,6 +42,86 @@ export function WorldEntityBars({ statId, height = 2.2, roles, resolveRole, maxD
|
|
|
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);
|
|
@@ -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;
|
|
@@ -0,0 +1,159 @@
|
|
|
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, useMemo, useRef } from "react";
|
|
4
|
+
import * as THREE from "three";
|
|
5
|
+
import { objectVisualScale } from "@jgengine/core/scene/objectStore";
|
|
6
|
+
import { WORLD_ITEM_ENTITY_NAME } from "@jgengine/core/game/worldItem";
|
|
7
|
+
import { useGameContext } from "@jgengine/react/provider";
|
|
8
|
+
import { useSceneEntityIds, useSceneObjectIds, useGameStore, usePlayer, useTarget } from "@jgengine/react/hooks";
|
|
9
|
+
import { colorFromId } from "../worldSky.js";
|
|
10
|
+
import { DefaultSurface, detailMaps } from "../render/defaultSurface.js";
|
|
11
|
+
import { EntitySprite, IsolatedEntityModel } from "../render/SceneModels.js";
|
|
12
|
+
import { resolveModel, resolveEntityModel, tryResolveCatalogModel } from "../render/resolveModel.js";
|
|
13
|
+
import { useRenderVisibility } from "../visibility/CullingProvider.js";
|
|
14
|
+
import { writeEntityPose } from "./entityPose.js";
|
|
15
|
+
import { POINTER_ENTITY_KEY, POINTER_OBJECT_KEY } from "../pointer/pointerService.js";
|
|
16
|
+
const GROUND_SIZE = 160;
|
|
17
|
+
const GROUND_SEGMENTS = 80;
|
|
18
|
+
function EntityMarker({ entity, custom, model, sprite, isLocal, targeted, selected, onSelect, }) {
|
|
19
|
+
const groupRef = useRef(null);
|
|
20
|
+
const ctx = useGameContext();
|
|
21
|
+
const visibleRef = useRenderVisibility();
|
|
22
|
+
const entityId = entity.id;
|
|
23
|
+
const role = entity.role;
|
|
24
|
+
const name = entity.name;
|
|
25
|
+
const color = isLocal ? "#4ade80" : role === "npc" ? colorFromId(name) : "#9ca3af";
|
|
26
|
+
useFrame(() => {
|
|
27
|
+
const group = groupRef.current;
|
|
28
|
+
if (group === null)
|
|
29
|
+
return;
|
|
30
|
+
const live = ctx.scene.entity.get(entityId);
|
|
31
|
+
if (live === null)
|
|
32
|
+
return;
|
|
33
|
+
writeEntityPose(group, live);
|
|
34
|
+
group.visible = visibleRef.current(entityId);
|
|
35
|
+
});
|
|
36
|
+
return (_jsxs("group", { ref: groupRef, userData: { [POINTER_ENTITY_KEY]: entityId }, onPointerDown: (event) => {
|
|
37
|
+
event.stopPropagation();
|
|
38
|
+
if (isLocal)
|
|
39
|
+
return;
|
|
40
|
+
const live = ctx.scene.entity.get(entityId);
|
|
41
|
+
if (live !== null)
|
|
42
|
+
onSelect(live);
|
|
43
|
+
}, children: [selected ? (_jsxs("mesh", { "rotation-x": -Math.PI / 2, "position-y": 0.02, children: [_jsx("ringGeometry", { args: [0.8, 0.95, 32] }), _jsx("meshBasicMaterial", { color: "#34d399", transparent: true, opacity: 0.9 })] })) : null, custom !== undefined && custom !== null ? (custom) : model !== undefined ? (_jsx(IsolatedEntityModel, { model: model, instanceId: entityId, fallback: sprite !== undefined ? _jsx(EntitySprite, { sprite: sprite }) : undefined })) : sprite !== undefined ? (_jsx(EntitySprite, { sprite: sprite })) : role === "prop" ? (_jsxs("mesh", { "position-y": 0.5, castShadow: true, receiveShadow: true, children: [_jsx("sphereGeometry", { args: [0.45, 24, 24] }), _jsx(DefaultSurface, { color: color })] })) : (_jsxs("group", { scale: ctx.scene.entity.visualScaleOf(entityId), children: [_jsxs("mesh", { "position-y": 0.95, castShadow: true, receiveShadow: true, children: [_jsx("capsuleGeometry", { args: [0.35, 1.1, 6, 14] }), _jsx(DefaultSurface, { color: color, roughness: 0.6 })] }), _jsxs("mesh", { position: [0, 1.35, 0.32], children: [_jsx("boxGeometry", { args: [0.16, 0.16, 0.16] }), _jsx("meshStandardMaterial", { color: "#f8fafc" })] })] })), targeted ? (_jsxs("mesh", { "rotation-x": -Math.PI / 2, "position-y": 0.03, children: [_jsx("ringGeometry", { args: [0.6, 0.75, 28] }), _jsx("meshBasicMaterial", { color: "#f87171" })] })) : null] }));
|
|
44
|
+
}
|
|
45
|
+
function ObjectMarker({ object, custom, model, style, }) {
|
|
46
|
+
const groupRef = useRef(null);
|
|
47
|
+
const ctx = useGameContext();
|
|
48
|
+
const visibleRef = useRenderVisibility();
|
|
49
|
+
const instanceId = object.instanceId;
|
|
50
|
+
const [scaleX, scaleY, scaleZ] = objectVisualScale(object.visual);
|
|
51
|
+
const color = object.visual?.color ?? style?.color ?? colorFromId(object.catalogId);
|
|
52
|
+
const opacity = object.visual?.opacity ?? style?.opacity ?? 1;
|
|
53
|
+
useFrame(() => {
|
|
54
|
+
const group = groupRef.current;
|
|
55
|
+
if (group === null)
|
|
56
|
+
return;
|
|
57
|
+
const live = ctx.scene.object.get(instanceId);
|
|
58
|
+
if (live === null)
|
|
59
|
+
return;
|
|
60
|
+
writeEntityPose(group, live);
|
|
61
|
+
group.visible = visibleRef.current(instanceId);
|
|
62
|
+
});
|
|
63
|
+
return (_jsx("group", { ref: groupRef, userData: { [POINTER_OBJECT_KEY]: instanceId }, children: custom !== undefined && custom !== null ? (custom) : model !== undefined ? (_jsx(IsolatedEntityModel, { model: model, instanceId: instanceId })) : style?.hidden === true ? null : (_jsxs("mesh", { "position-y": 0.5 * scaleY, scale: [scaleX, scaleY, scaleZ], castShadow: true, receiveShadow: true, children: [_jsx("boxGeometry", { args: [1, 1, 1] }), _jsx(DefaultSurface, { color: color, transparent: opacity < 1, opacity: opacity })] })) }));
|
|
64
|
+
}
|
|
65
|
+
function GroundPlane() {
|
|
66
|
+
const geometry = useMemo(() => {
|
|
67
|
+
const next = new THREE.PlaneGeometry(GROUND_SIZE, GROUND_SIZE, GROUND_SEGMENTS, GROUND_SEGMENTS);
|
|
68
|
+
const positions = next.attributes.position;
|
|
69
|
+
const base = new THREE.Color("#33452f");
|
|
70
|
+
const high = new THREE.Color("#4a5c3a");
|
|
71
|
+
const colors = new Float32Array(positions.count * 3);
|
|
72
|
+
const tmp = new THREE.Color();
|
|
73
|
+
for (let index = 0; index < positions.count; index += 1) {
|
|
74
|
+
const x = positions.getX(index);
|
|
75
|
+
const y = positions.getY(index);
|
|
76
|
+
const height = Math.sin(x * 0.16) * 0.12 +
|
|
77
|
+
Math.cos(y * 0.11) * 0.1 +
|
|
78
|
+
Math.sin((x + y) * 0.05) * 0.16;
|
|
79
|
+
positions.setZ(index, height);
|
|
80
|
+
const blend = 0.5 + Math.sin(x * 0.21) * 0.25 + Math.cos(y * 0.17) * 0.25;
|
|
81
|
+
tmp.copy(base).lerp(high, Math.min(1, Math.max(0, blend)));
|
|
82
|
+
colors[index * 3] = tmp.r;
|
|
83
|
+
colors[index * 3 + 1] = tmp.g;
|
|
84
|
+
colors[index * 3 + 2] = tmp.b;
|
|
85
|
+
}
|
|
86
|
+
next.setAttribute("color", new THREE.BufferAttribute(colors, 3));
|
|
87
|
+
next.computeVertexNormals();
|
|
88
|
+
return next;
|
|
89
|
+
}, []);
|
|
90
|
+
const material = useMemo(() => {
|
|
91
|
+
const normal = detailMaps().normal.clone();
|
|
92
|
+
normal.repeat.set(48, 48);
|
|
93
|
+
normal.needsUpdate = true;
|
|
94
|
+
return new THREE.MeshStandardMaterial({
|
|
95
|
+
vertexColors: true,
|
|
96
|
+
roughness: 0.95,
|
|
97
|
+
metalness: 0,
|
|
98
|
+
normalMap: normal,
|
|
99
|
+
normalScale: new THREE.Vector2(0.8, 0.8),
|
|
100
|
+
envMapIntensity: 0.4,
|
|
101
|
+
});
|
|
102
|
+
}, []);
|
|
103
|
+
useEffect(() => () => material.dispose(), [material]);
|
|
104
|
+
return _jsx("mesh", { "rotation-x": -Math.PI / 2, geometry: geometry, material: material, receiveShadow: true });
|
|
105
|
+
}
|
|
106
|
+
function RockField() {
|
|
107
|
+
const rocks = useMemo(() => Array.from({ length: 18 }, (_, index) => {
|
|
108
|
+
const angle = index * 2.399963229728653;
|
|
109
|
+
const radius = 10 + ((index * 17) % 58);
|
|
110
|
+
return {
|
|
111
|
+
id: `rock-${index}`,
|
|
112
|
+
x: Math.cos(angle) * radius,
|
|
113
|
+
z: Math.sin(angle) * radius,
|
|
114
|
+
scale: 0.45 + ((index * 13) % 11) / 10,
|
|
115
|
+
rotation: angle,
|
|
116
|
+
};
|
|
117
|
+
}), []);
|
|
118
|
+
return (_jsx(_Fragment, { children: rocks.map((rock) => (_jsxs("mesh", { position: [rock.x, 0.25 * rock.scale, rock.z], rotation: [0.1, rock.rotation, -0.08], scale: [rock.scale * 1.4, rock.scale * 0.7, rock.scale], castShadow: true, receiveShadow: true, children: [_jsx("dodecahedronGeometry", { args: [0.8, 1] }), _jsx(DefaultSurface, { color: "#6b6f63", roughness: 0.95 })] }, rock.id))) }));
|
|
119
|
+
}
|
|
120
|
+
function WorldEnvironment({ environment: Environment }) {
|
|
121
|
+
if (Environment !== undefined)
|
|
122
|
+
return _jsx(Environment, {});
|
|
123
|
+
return (_jsxs(_Fragment, { children: [_jsx(GroundPlane, {}), _jsx(RockField, {})] }));
|
|
124
|
+
}
|
|
125
|
+
function WorldActors({ entitySprites, entityModels, objectModels, objectStyles, assets, renderEntity, renderObject, selectedIds, hideLocalActor, }) {
|
|
126
|
+
const ctx = useGameContext();
|
|
127
|
+
const entityIds = useSceneEntityIds();
|
|
128
|
+
const objectIds = useSceneObjectIds();
|
|
129
|
+
const player = usePlayer();
|
|
130
|
+
const targetId = useTarget(player.userId);
|
|
131
|
+
const controlledId = useGameStore((c) => c.player.possession.active(player.userId));
|
|
132
|
+
const handleSelect = (entity) => {
|
|
133
|
+
const relation = ctx.scene.entity.canReceive(entity.id, "damage") === null ? "hostile" : "friendly";
|
|
134
|
+
ctx.scene.entity.setTarget(controlledId, relation === "hostile" || entity.role === "npc" ? entity.id : null);
|
|
135
|
+
};
|
|
136
|
+
return (_jsxs(_Fragment, { children: [entityIds.map((entityId) => {
|
|
137
|
+
const entity = ctx.scene.entity.get(entityId);
|
|
138
|
+
if (entity === null || entity.name === WORLD_ITEM_ENTITY_NAME)
|
|
139
|
+
return null;
|
|
140
|
+
if (hideLocalActor && entityId === controlledId)
|
|
141
|
+
return null;
|
|
142
|
+
return (_jsx(EntityMarker, { entity: entity, custom: renderEntity?.(entity), model: resolveEntityModel(entityModels?.[entity.name], assets, entity.name), sprite: entitySprites?.[entity.name], isLocal: entityId === controlledId, targeted: entityId === targetId, selected: selectedIds.has(entityId), onSelect: handleSelect }, entityId));
|
|
143
|
+
}), objectIds.map((instanceId) => {
|
|
144
|
+
const object = ctx.scene.object.get(instanceId);
|
|
145
|
+
if (object === null)
|
|
146
|
+
return null;
|
|
147
|
+
const model = resolveModel(objectModels?.[object.catalogId], assets, {
|
|
148
|
+
seam: "objectModels",
|
|
149
|
+
key: object.catalogId,
|
|
150
|
+
}) ?? tryResolveCatalogModel(object.catalogId, assets);
|
|
151
|
+
return (_jsx(ObjectMarker, { object: object, custom: renderObject?.(object), model: model, style: objectStyles?.[object.catalogId] }, instanceId));
|
|
152
|
+
})] }));
|
|
153
|
+
}
|
|
154
|
+
export function WorldView({ entitySprites, entityModels, objectModels, objectStyles, environment, assets, renderEntity, renderObject, selectedIds, hideLocalActor, }) {
|
|
155
|
+
return (_jsxs(_Fragment, { children: [_jsx(WorldEnvironment, { environment: environment }), _jsx(WorldActors, { entitySprites: entitySprites, entityModels: entityModels, objectModels: objectModels, objectStyles: objectStyles, assets: assets, renderEntity: renderEntity, renderObject: renderObject, selectedIds: selectedIds, hideLocalActor: hideLocalActor })] }));
|
|
156
|
+
}
|
|
157
|
+
export function RemotePlayers({ rows }) {
|
|
158
|
+
return (_jsx(_Fragment, { children: rows.map((row) => (_jsxs("group", { position: [row.position.x, row.position.y, row.position.z], "rotation-y": row.rotationY, children: [_jsxs("mesh", { "position-y": 0.95, children: [_jsx("capsuleGeometry", { args: [0.35, 1.1, 6, 14] }), _jsx("meshStandardMaterial", { color: colorFromId(row.userId) })] }), _jsxs("mesh", { position: [0, 1.35, 0.32], children: [_jsx("boxGeometry", { args: [0.16, 0.16, 0.16] }), _jsx("meshStandardMaterial", { color: "#f8fafc" })] })] }, row.userId))) }));
|
|
159
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders `combat.vfx` bursts as additive sprite-particle effects in the world scene. Auto-mounted by the
|
|
3
|
+
* player shell alongside the other world overlays; games emit effects via `ctx.scene.entity.vfx(...)`.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export declare function WorldSpellVfx({ tailMs }: {
|
|
8
|
+
tailMs?: number;
|
|
9
|
+
}): import("react").JSX.Element;
|