@jgengine/shell 0.5.0 → 0.7.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 +191 -2
- package/README.md +27 -0
- package/dist/GamePlayerShell.js +224 -43
- package/dist/audio/AudioComponents.d.ts +12 -0
- package/dist/audio/AudioComponents.js +53 -0
- package/dist/audio/audioEngine.d.ts +23 -0
- package/dist/audio/audioEngine.js +110 -0
- package/dist/camera/GameCameraRig.d.ts +11 -0
- package/dist/camera/GameCameraRig.js +42 -0
- package/dist/camera/GameOrbitCamera.d.ts +3 -1
- package/dist/camera/GameOrbitCamera.js +4 -2
- package/dist/camera/cameraRigs.d.ts +22 -0
- package/dist/camera/cameraRigs.js +485 -0
- package/dist/camera/index.d.ts +5 -0
- package/dist/camera/index.js +5 -0
- package/dist/camera/rigMath.d.ts +130 -0
- package/dist/camera/rigMath.js +338 -0
- package/dist/camera/shakeChannel.d.ts +29 -0
- package/dist/camera/shakeChannel.js +38 -0
- package/dist/demo/builderDemo.d.ts +2 -0
- package/dist/demo/builderDemo.js +189 -0
- package/dist/demo/demoGame.js +10 -2
- package/dist/demo/environmentShowcase.d.ts +2 -0
- package/dist/demo/environmentShowcase.js +15 -0
- package/dist/demo/mapDemo.d.ts +2 -0
- package/dist/demo/mapDemo.js +206 -0
- package/dist/demo/pointerDemo.d.ts +2 -0
- package/dist/demo/pointerDemo.js +151 -0
- package/dist/demo/sensorShowcase.d.ts +2 -0
- package/dist/demo/sensorShowcase.js +131 -0
- package/dist/demo/survivalDemo.d.ts +2 -0
- package/dist/demo/survivalDemo.js +291 -0
- package/dist/environment/EnvironmentScene.d.ts +5 -0
- package/dist/environment/EnvironmentScene.js +75 -0
- package/dist/environment/index.d.ts +1 -0
- package/dist/environment/index.js +1 -0
- package/dist/environment.d.ts +1 -0
- package/dist/environment.js +1 -0
- package/dist/map/MapMarkerBeacons.d.ts +12 -0
- package/dist/map/MapMarkerBeacons.js +16 -0
- package/dist/map/index.d.ts +2 -0
- package/dist/map/index.js +2 -0
- package/dist/map/terrainMap.d.ts +23 -0
- package/dist/map/terrainMap.js +79 -0
- package/dist/pointer/PointerOverlays.d.ts +12 -0
- package/dist/pointer/PointerOverlays.js +17 -0
- package/dist/pointer/PointerProbe.d.ts +4 -0
- package/dist/pointer/PointerProbe.js +27 -0
- package/dist/pointer/pointerService.d.ts +24 -0
- package/dist/pointer/pointerService.js +68 -0
- package/dist/registry.d.ts +4 -2
- package/dist/replay/useSessionRecorder.d.ts +14 -0
- package/dist/replay/useSessionRecorder.js +22 -0
- package/dist/structures/GeneratedBuilding.d.ts +44 -0
- package/dist/structures/GeneratedBuilding.js +86 -0
- package/dist/structures/PlacementGhost.d.ts +8 -0
- package/dist/structures/PlacementGhost.js +11 -0
- package/dist/structures/index.d.ts +2 -0
- package/dist/structures/index.js +2 -0
- package/dist/structures.d.ts +1 -0
- package/dist/structures.js +1 -0
- package/dist/terrain/CarvedTerrain.d.ts +22 -0
- package/dist/terrain/CarvedTerrain.js +19 -0
- package/dist/terrain/EditableGround.d.ts +11 -0
- package/dist/terrain/EditableGround.js +38 -0
- package/dist/terrain/GrassField.d.ts +21 -0
- package/dist/terrain/GrassField.js +31 -0
- package/dist/terrain/ProceduralGround.d.ts +9 -0
- package/dist/terrain/ProceduralGround.js +17 -0
- package/dist/terrain/TerraformBrushCursor.d.ts +8 -0
- package/dist/terrain/TerraformBrushCursor.js +13 -0
- package/dist/terrain/grassGeometry.d.ts +27 -0
- package/dist/terrain/grassGeometry.js +77 -0
- package/dist/terrain/grassMaterial.d.ts +33 -0
- package/dist/terrain/grassMaterial.js +108 -0
- package/dist/terrain/index.d.ts +10 -0
- package/dist/terrain/index.js +10 -0
- package/dist/terrain/random.d.ts +4 -0
- package/dist/terrain/random.js +27 -0
- package/dist/terrain/terrainMath.d.ts +47 -0
- package/dist/terrain/terrainMath.js +172 -0
- package/dist/terrain.d.ts +1 -0
- package/dist/terrain.js +1 -0
- package/dist/vision/FrustumSensorHud.d.ts +21 -0
- package/dist/vision/FrustumSensorHud.js +67 -0
- package/dist/vision/HiddenStateProbeHud.d.ts +11 -0
- package/dist/vision/HiddenStateProbeHud.js +11 -0
- package/dist/vision/RevealVision.d.ts +28 -0
- package/dist/vision/RevealVision.js +53 -0
- package/dist/water/Ocean.d.ts +6 -0
- package/dist/water/Ocean.js +30 -0
- package/dist/water/OceanConfig.d.ts +90 -0
- package/dist/water/OceanConfig.js +131 -0
- package/dist/water/OceanMaterial.d.ts +54 -0
- package/dist/water/OceanMaterial.js +51 -0
- package/dist/water/OceanShader.d.ts +2 -0
- package/dist/water/OceanShader.js +78 -0
- package/dist/water/index.d.ts +3 -0
- package/dist/water/index.js +3 -0
- package/dist/water.d.ts +1 -0
- package/dist/water.js +1 -0
- package/dist/weather/FireSpreadLayer.d.ts +14 -0
- package/dist/weather/FireSpreadLayer.js +68 -0
- package/dist/weather/LightningStrike.d.ts +17 -0
- package/dist/weather/LightningStrike.js +130 -0
- package/dist/weather/RainField.d.ts +20 -0
- package/dist/weather/RainField.js +107 -0
- package/dist/weather/SnowField.d.ts +19 -0
- package/dist/weather/SnowField.js +108 -0
- package/dist/weather/WeatherLayer.d.ts +17 -0
- package/dist/weather/WeatherLayer.js +16 -0
- package/dist/weather/index.d.ts +6 -0
- package/dist/weather/index.js +6 -0
- package/dist/weather/weatherGeometry.d.ts +2 -0
- package/dist/weather/weatherGeometry.js +14 -0
- package/dist/weather/weatherMath.d.ts +7 -0
- package/dist/weather/weatherMath.js +30 -0
- package/dist/weather/weatherUniforms.d.ts +18 -0
- package/dist/weather/weatherUniforms.js +34 -0
- package/dist/world/InstancedBodies.d.ts +17 -0
- package/dist/world/InstancedBodies.js +84 -0
- package/dist/world/InstancedJoints.d.ts +14 -0
- package/dist/world/InstancedJoints.js +32 -0
- package/dist/world/WorldHud.d.ts +2 -0
- package/dist/world/WorldHud.js +70 -12
- package/dist/world/WorldItems.d.ts +5 -0
- package/dist/world/WorldItems.js +31 -0
- package/dist/world/floatTextStyle.d.ts +14 -0
- package/dist/world/floatTextStyle.js +39 -0
- package/package.json +12 -4
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsx as _jsx } 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
|
+
const SLEEPING_COLOR = new THREE.Color(0.32, 0.4, 0.62);
|
|
6
|
+
const AWAKE_COLOR = new THREE.Color(0.45, 0.78, 0.5);
|
|
7
|
+
const CONTACT_COLOR = new THREE.Color(0.96, 0.55, 0.18);
|
|
8
|
+
const DEFAULT_BASE = new THREE.Color(0.62, 0.64, 0.68);
|
|
9
|
+
/**
|
|
10
|
+
* Renders a PhysicsWorld's box bodies as a single InstancedMesh — one draw call per batch.
|
|
11
|
+
* Transforms are written directly into `instanceMatrix.array` each frame (bodies never touch
|
|
12
|
+
* the per-entity React path). Reusable by any game: hand it a physics world and go.
|
|
13
|
+
*/
|
|
14
|
+
export function InstancedBodies({ world, debugTint = false, baseColors, epoch = 0 }) {
|
|
15
|
+
const meshRef = useRef(null);
|
|
16
|
+
const geometry = useMemo(() => new THREE.BoxGeometry(1, 1, 1), []);
|
|
17
|
+
const material = useMemo(() => new THREE.MeshStandardMaterial({ roughness: 0.85, metalness: 0, vertexColors: false }), []);
|
|
18
|
+
const capacity = world.capacity;
|
|
19
|
+
const instanceColor = useMemo(() => new THREE.InstancedBufferAttribute(new Float32Array(capacity * 3), 3), [capacity]);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const mesh = meshRef.current;
|
|
22
|
+
if (mesh === null)
|
|
23
|
+
return;
|
|
24
|
+
mesh.instanceColor = instanceColor;
|
|
25
|
+
mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
|
|
26
|
+
instanceColor.setUsage(THREE.DynamicDrawUsage);
|
|
27
|
+
}, [instanceColor]);
|
|
28
|
+
const lastTintRef = useRef(null);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
lastTintRef.current = null;
|
|
31
|
+
}, [epoch, debugTint]);
|
|
32
|
+
useFrame(() => {
|
|
33
|
+
const mesh = meshRef.current;
|
|
34
|
+
if (mesh === null)
|
|
35
|
+
return;
|
|
36
|
+
const count = world.count;
|
|
37
|
+
mesh.count = count;
|
|
38
|
+
const m = mesh.instanceMatrix.array;
|
|
39
|
+
const { posX, posY, posZ, halfX, halfY, halfZ } = world;
|
|
40
|
+
for (let i = 0; i < count; i += 1) {
|
|
41
|
+
const o = i * 16;
|
|
42
|
+
m[o] = halfX[i] * 2;
|
|
43
|
+
m[o + 5] = halfY[i] * 2;
|
|
44
|
+
m[o + 10] = halfZ[i] * 2;
|
|
45
|
+
m[o + 12] = posX[i];
|
|
46
|
+
m[o + 13] = posY[i];
|
|
47
|
+
m[o + 14] = posZ[i];
|
|
48
|
+
m[o + 15] = 1;
|
|
49
|
+
}
|
|
50
|
+
mesh.instanceMatrix.needsUpdate = true;
|
|
51
|
+
const c = instanceColor.array;
|
|
52
|
+
if (debugTint) {
|
|
53
|
+
const { contact } = world;
|
|
54
|
+
for (let i = 0; i < count; i += 1) {
|
|
55
|
+
const col = contact[i] !== 0 ? CONTACT_COLOR : world.isSleeping(i) ? SLEEPING_COLOR : AWAKE_COLOR;
|
|
56
|
+
const o = i * 3;
|
|
57
|
+
c[o] = col.r;
|
|
58
|
+
c[o + 1] = col.g;
|
|
59
|
+
c[o + 2] = col.b;
|
|
60
|
+
}
|
|
61
|
+
instanceColor.needsUpdate = true;
|
|
62
|
+
}
|
|
63
|
+
else if (lastTintRef.current !== false) {
|
|
64
|
+
if (baseColors !== undefined) {
|
|
65
|
+
c.set(baseColors.subarray(0, capacity * 3));
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
for (let i = 0; i < capacity; i += 1) {
|
|
69
|
+
const o = i * 3;
|
|
70
|
+
c[o] = DEFAULT_BASE.r;
|
|
71
|
+
c[o + 1] = DEFAULT_BASE.g;
|
|
72
|
+
c[o + 2] = DEFAULT_BASE.b;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
instanceColor.needsUpdate = true;
|
|
76
|
+
}
|
|
77
|
+
lastTintRef.current = debugTint;
|
|
78
|
+
});
|
|
79
|
+
useEffect(() => () => {
|
|
80
|
+
geometry.dispose();
|
|
81
|
+
material.dispose();
|
|
82
|
+
}, [geometry, material]);
|
|
83
|
+
return (_jsx("instancedMesh", { ref: meshRef, args: [geometry, material, capacity], frustumCulled: false, castShadow: false, receiveShadow: false }));
|
|
84
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import type { PhysicsWorld } from "@jgengine/core/physics/physicsWorld";
|
|
3
|
+
export interface InstancedJointsProps {
|
|
4
|
+
/** Physics world whose active joints are drawn as line segments between their world anchors. */
|
|
5
|
+
world: PhysicsWorld;
|
|
6
|
+
/** Line color. Default warm yellow. */
|
|
7
|
+
color?: THREE.ColorRepresentation;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Debug overlay drawing a PhysicsWorld's joints (suspension, ragdoll links, carry tethers) as one
|
|
11
|
+
* LineSegments batch. Endpoints are streamed each frame from `world.readJointSegments`; pair with
|
|
12
|
+
* `InstancedBodies` to see the constraint structure over the bodies.
|
|
13
|
+
*/
|
|
14
|
+
export declare function InstancedJoints({ world, color }: InstancedJointsProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { 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
|
+
/**
|
|
6
|
+
* Debug overlay drawing a PhysicsWorld's joints (suspension, ragdoll links, carry tethers) as one
|
|
7
|
+
* LineSegments batch. Endpoints are streamed each frame from `world.readJointSegments`; pair with
|
|
8
|
+
* `InstancedBodies` to see the constraint structure over the bodies.
|
|
9
|
+
*/
|
|
10
|
+
export function InstancedJoints({ world, color = "#f5c542" }) {
|
|
11
|
+
const geometryRef = useRef(null);
|
|
12
|
+
const positions = useMemo(() => new Float32Array(world.jointCapacity * 6), [world.jointCapacity]);
|
|
13
|
+
const attribute = useMemo(() => new THREE.BufferAttribute(positions, 3), [positions]);
|
|
14
|
+
const material = useMemo(() => new THREE.LineBasicMaterial({ color }), [color]);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const geometry = geometryRef.current;
|
|
17
|
+
if (geometry === null)
|
|
18
|
+
return;
|
|
19
|
+
geometry.setAttribute("position", attribute);
|
|
20
|
+
attribute.setUsage(THREE.DynamicDrawUsage);
|
|
21
|
+
}, [attribute]);
|
|
22
|
+
useFrame(() => {
|
|
23
|
+
const geometry = geometryRef.current;
|
|
24
|
+
if (geometry === null)
|
|
25
|
+
return;
|
|
26
|
+
const count = world.readJointSegments(positions);
|
|
27
|
+
attribute.needsUpdate = true;
|
|
28
|
+
geometry.setDrawRange(0, count * 2);
|
|
29
|
+
});
|
|
30
|
+
useEffect(() => () => material.dispose(), [material]);
|
|
31
|
+
return (_jsxs("lineSegments", { frustumCulled: false, children: [_jsx("bufferGeometry", { ref: geometryRef }), _jsx("primitive", { object: material, attach: "material" })] }));
|
|
32
|
+
}
|
package/dist/world/WorldHud.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export declare function WorldFloatText({ height, lifeMs }: {
|
|
|
6
6
|
height?: number;
|
|
7
7
|
lifeMs?: number;
|
|
8
8
|
}): import("react").JSX.Element;
|
|
9
|
+
export declare function WorldTelegraphs(): import("react").JSX.Element;
|
|
10
|
+
export declare function CombatCameraShake(): null;
|
|
9
11
|
export declare function ProjectileTracers({ lifeMs }: {
|
|
10
12
|
lifeMs?: number;
|
|
11
13
|
}): import("react").JSX.Element;
|
package/dist/world/WorldHud.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Html, Line } from "@react-three/drei";
|
|
3
|
-
import {
|
|
3
|
+
import { useFrame, useThree } from "@react-three/fiber";
|
|
4
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
5
|
import * as THREE from "three";
|
|
5
6
|
import { useGameContext } from "@jgengine/react/provider";
|
|
6
7
|
import { useEntityStat, useSceneEntities } from "@jgengine/react/hooks";
|
|
8
|
+
import { resolveFloatTextStyle } from "./floatTextStyle.js";
|
|
7
9
|
const MUZZLE_HEIGHT = 1.4;
|
|
8
10
|
function EntityBar({ entity, statId, height }) {
|
|
9
11
|
const stat = useEntityStat(entity.id, statId);
|
|
@@ -19,21 +21,23 @@ export function WorldEntityBars({ statId, height = 2.2 }) {
|
|
|
19
21
|
const playerId = ctx.player.userId;
|
|
20
22
|
return (_jsx(_Fragment, { children: entities.map((entity) => entity.id === playerId ? null : (_jsx(EntityBar, { entity: entity, statId: statId, height: height }, entity.id))) }));
|
|
21
23
|
}
|
|
22
|
-
function FloatTextItem({
|
|
24
|
+
function FloatTextItem({ event, lifeMs }) {
|
|
23
25
|
const [shown, setShown] = useState(false);
|
|
26
|
+
const style = useMemo(() => resolveFloatTextStyle(event), [event]);
|
|
24
27
|
useEffect(() => {
|
|
25
28
|
const raf = requestAnimationFrame(() => setShown(true));
|
|
26
29
|
return () => cancelAnimationFrame(raf);
|
|
27
30
|
}, []);
|
|
28
|
-
return (_jsx("span", {
|
|
31
|
+
return (_jsx("span", { style: {
|
|
29
32
|
display: "inline-block",
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
color: style.color,
|
|
34
|
+
fontWeight: style.fontWeight,
|
|
35
|
+
fontSize: `${style.fontSizePx}px`,
|
|
36
|
+
textShadow: style.glow,
|
|
33
37
|
transition: `transform ${lifeMs}ms ease-out, opacity ${lifeMs}ms ease-out`,
|
|
34
38
|
transform: shown ? "translateY(-30px)" : "translateY(0)",
|
|
35
39
|
opacity: shown ? 0 : 1,
|
|
36
|
-
}, children: text }));
|
|
40
|
+
}, children: event.text }));
|
|
37
41
|
}
|
|
38
42
|
export function WorldFloatText({ height = 1.9, lifeMs = 950 }) {
|
|
39
43
|
const ctx = useGameContext();
|
|
@@ -42,14 +46,68 @@ export function WorldFloatText({ height = 1.9, lifeMs = 950 }) {
|
|
|
42
46
|
useEffect(() => {
|
|
43
47
|
return ctx.game.events.on("entity.floatText", (event) => {
|
|
44
48
|
const id = nextId.current++;
|
|
45
|
-
setFloaters((current) => [
|
|
46
|
-
...current,
|
|
47
|
-
{ id, position: event.position, text: event.text, kind: event.kind },
|
|
48
|
-
]);
|
|
49
|
+
setFloaters((current) => [...current, { id, position: event.position, event }]);
|
|
49
50
|
window.setTimeout(() => setFloaters((current) => current.filter((floater) => floater.id !== id)), lifeMs);
|
|
50
51
|
});
|
|
51
52
|
}, [ctx, lifeMs]);
|
|
52
|
-
return (_jsx(_Fragment, { children: floaters.map((floater) => (_jsx(Html, { position: [floater.position[0], floater.position[1] + height, floater.position[2]], center: true, distanceFactor: 12, zIndexRange: [30, 0], children: _jsx(FloatTextItem, {
|
|
53
|
+
return (_jsx(_Fragment, { children: floaters.map((floater) => (_jsx(Html, { position: [floater.position[0], floater.position[1] + height, floater.position[2]], center: true, distanceFactor: 12, zIndexRange: [30, 0], children: _jsx(FloatTextItem, { event: floater.event, lifeMs: lifeMs }) }, floater.id))) }));
|
|
54
|
+
}
|
|
55
|
+
function TelegraphGeometry({ shape }) {
|
|
56
|
+
if (shape.kind === "circle")
|
|
57
|
+
return _jsx("circleGeometry", { args: [shape.radius, 48] });
|
|
58
|
+
if (shape.kind === "ring")
|
|
59
|
+
return _jsx("ringGeometry", { args: [shape.innerRadius, shape.radius, 48] });
|
|
60
|
+
if (shape.kind === "cone")
|
|
61
|
+
return _jsx("circleGeometry", { args: [shape.radius, 40, -shape.angle / 2, shape.angle] });
|
|
62
|
+
return _jsx("planeGeometry", { args: [shape.width, shape.length] });
|
|
63
|
+
}
|
|
64
|
+
function TelegraphDecal({ active, nowMs }) {
|
|
65
|
+
const progress = Math.max(0, Math.min(1, (nowMs - active.bornMs) / active.event.windupMs));
|
|
66
|
+
const [x, y, z] = active.event.position;
|
|
67
|
+
const dir = active.event.dir ?? 0;
|
|
68
|
+
const shape = active.event.shape;
|
|
69
|
+
const forwardOffset = shape.kind === "line" ? shape.length / 2 : 0;
|
|
70
|
+
const color = active.event.kind === "danger" ? "#ef4444" : "#f59e0b";
|
|
71
|
+
const pulse = 0.45 + 0.5 * progress;
|
|
72
|
+
return (_jsxs("mesh", { position: [x + Math.sin(dir) * forwardOffset, y + 0.06, z + Math.cos(dir) * forwardOffset], rotation: [-Math.PI / 2, 0, shape.kind === "line" || shape.kind === "cone" ? -dir : 0], renderOrder: 999, children: [_jsx(TelegraphGeometry, { shape: shape }), _jsx("meshBasicMaterial", { color: color, transparent: true, opacity: pulse, side: THREE.DoubleSide, depthWrite: false, depthTest: false, toneMapped: false })] }));
|
|
73
|
+
}
|
|
74
|
+
export function WorldTelegraphs() {
|
|
75
|
+
const ctx = useGameContext();
|
|
76
|
+
const [telegraphs, setTelegraphs] = useState([]);
|
|
77
|
+
const key = useRef(0);
|
|
78
|
+
const [nowMs, setNowMs] = useState(() => performance.now());
|
|
79
|
+
useFrame(() => setNowMs(performance.now()));
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
return ctx.game.events.on("combat.telegraph", (event) => {
|
|
82
|
+
const entry = { key: key.current++, event, bornMs: performance.now() };
|
|
83
|
+
setTelegraphs((current) => [...current, entry]);
|
|
84
|
+
window.setTimeout(() => setTelegraphs((current) => current.filter((t) => t.key !== entry.key)), event.windupMs + 120);
|
|
85
|
+
});
|
|
86
|
+
}, [ctx]);
|
|
87
|
+
return (_jsx(_Fragment, { children: telegraphs.map((active) => (_jsx(TelegraphDecal, { active: active, nowMs: nowMs }, active.key))) }));
|
|
88
|
+
}
|
|
89
|
+
export function CombatCameraShake() {
|
|
90
|
+
const ctx = useGameContext();
|
|
91
|
+
const camera = useThree((state) => state.camera);
|
|
92
|
+
const trauma = useRef(0);
|
|
93
|
+
const decay = useRef(4);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
return ctx.game.events.on("combat.hitReaction", (event) => {
|
|
96
|
+
if (event.shake === undefined)
|
|
97
|
+
return;
|
|
98
|
+
trauma.current = Math.min(1, trauma.current + event.shake.amplitude);
|
|
99
|
+
decay.current = event.shake.decay;
|
|
100
|
+
});
|
|
101
|
+
}, [ctx]);
|
|
102
|
+
useFrame((_state, dt) => {
|
|
103
|
+
if (trauma.current <= 0.0001)
|
|
104
|
+
return;
|
|
105
|
+
const magnitude = trauma.current * trauma.current;
|
|
106
|
+
camera.position.x += (Math.random() * 2 - 1) * magnitude * 0.4;
|
|
107
|
+
camera.position.y += (Math.random() * 2 - 1) * magnitude * 0.4;
|
|
108
|
+
trauma.current = Math.max(0, trauma.current - decay.current * dt);
|
|
109
|
+
});
|
|
110
|
+
return null;
|
|
53
111
|
}
|
|
54
112
|
export function ProjectileTracers({ lifeMs = 130 }) {
|
|
55
113
|
const ctx = useGameContext();
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { WorldItemRenderConfig } from "@jgengine/core/game/playableGame";
|
|
2
|
+
/** Rarity→beam/color/label render binding + loot-filter overlay (#32/#33) for every dropped `worldItem`. */
|
|
3
|
+
export declare function WorldItems({ config }: {
|
|
4
|
+
config?: WorldItemRenderConfig;
|
|
5
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Html, Line } from "@react-three/drei";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { resolveWorldItemPresentation } from "@jgengine/core/game/worldItem";
|
|
5
|
+
import { useGameContext } from "@jgengine/react/provider";
|
|
6
|
+
import { useWorldItems } from "@jgengine/react/hooks";
|
|
7
|
+
import { POINTER_ENTITY_KEY } from "../pointer/pointerService.js";
|
|
8
|
+
const DEFAULT_BEAM_HEIGHT = 2.5;
|
|
9
|
+
const FALLBACK_COLOR = "#e5e7eb";
|
|
10
|
+
function WorldItemMarker({ instanceId, position, color, beam, label, beamHeight, }) {
|
|
11
|
+
const beamPoints = useMemo(() => [
|
|
12
|
+
[0, 0, 0],
|
|
13
|
+
[0, beamHeight, 0],
|
|
14
|
+
], [beamHeight]);
|
|
15
|
+
return (_jsxs("group", { position: [position[0], position[1], position[2]], userData: { [POINTER_ENTITY_KEY]: instanceId }, children: [_jsxs("mesh", { "rotation-x": -Math.PI / 2, "position-y": 0.03, children: [_jsx("ringGeometry", { args: [0.26, 0.4, 24] }), _jsx("meshBasicMaterial", { color: color, transparent: true, opacity: 0.85 })] }), _jsxs("mesh", { "position-y": 0.32, children: [_jsx("octahedronGeometry", { args: [0.22, 0] }), _jsx("meshStandardMaterial", { color: color, emissive: color, emissiveIntensity: 0.6 })] }), beam ? _jsx(Line, { points: beamPoints, color: color, lineWidth: 2, transparent: true, opacity: 0.55 }) : null, label !== undefined && label.length > 0 ? (_jsx(Html, { position: [0, beamHeight + 0.2, 0], center: true, distanceFactor: 12, zIndexRange: [15, 0], children: _jsx("div", { className: "whitespace-nowrap rounded-sm border px-1.5 py-0.5 text-[11px] font-bold shadow", style: { borderColor: color, color, backgroundColor: "rgba(10,10,14,0.75)" }, children: label }) })) : null] }));
|
|
16
|
+
}
|
|
17
|
+
/** Rarity→beam/color/label render binding + loot-filter overlay (#32/#33) for every dropped `worldItem`. */
|
|
18
|
+
export function WorldItems({ config }) {
|
|
19
|
+
const ctx = useGameContext();
|
|
20
|
+
const items = useWorldItems();
|
|
21
|
+
const beamHeight = config?.beamHeight ?? DEFAULT_BEAM_HEIGHT;
|
|
22
|
+
return (_jsx(_Fragment, { children: items.map((item) => {
|
|
23
|
+
const entity = ctx.scene.entity.get(item.instanceId);
|
|
24
|
+
if (entity === null)
|
|
25
|
+
return null;
|
|
26
|
+
const presentation = resolveWorldItemPresentation(item, config?.rarityStyle, config?.filter);
|
|
27
|
+
if (presentation.hidden)
|
|
28
|
+
return null;
|
|
29
|
+
return (_jsx(WorldItemMarker, { instanceId: item.instanceId, position: entity.position, color: presentation.color ?? FALLBACK_COLOR, beam: presentation.beam, label: presentation.label, beamHeight: beamHeight }, item.instanceId));
|
|
30
|
+
}) }));
|
|
31
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface FloatTextInfo {
|
|
2
|
+
kind: string;
|
|
3
|
+
hitType?: string;
|
|
4
|
+
element?: string;
|
|
5
|
+
crit?: boolean;
|
|
6
|
+
scale?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface FloatTextStyle {
|
|
9
|
+
color: string;
|
|
10
|
+
fontSizePx: number;
|
|
11
|
+
fontWeight: number;
|
|
12
|
+
glow: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function resolveFloatTextStyle(info: FloatTextInfo): FloatTextStyle;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const BASE_SIZE = 18;
|
|
2
|
+
const ELEMENT_COLORS = {
|
|
3
|
+
fire: "#fb923c",
|
|
4
|
+
frost: "#7dd3fc",
|
|
5
|
+
ice: "#7dd3fc",
|
|
6
|
+
lightning: "#fde047",
|
|
7
|
+
shock: "#fde047",
|
|
8
|
+
poison: "#a3e635",
|
|
9
|
+
rot: "#a3e635",
|
|
10
|
+
bleed: "#f87171",
|
|
11
|
+
arcane: "#c084fc",
|
|
12
|
+
holy: "#fef08a",
|
|
13
|
+
void: "#c084fc",
|
|
14
|
+
};
|
|
15
|
+
export function resolveFloatTextStyle(info) {
|
|
16
|
+
const crit = info.crit === true || info.hitType === "crit";
|
|
17
|
+
let color;
|
|
18
|
+
if (info.kind === "heal") {
|
|
19
|
+
color = "#6ee7b7";
|
|
20
|
+
}
|
|
21
|
+
else if (info.element !== undefined && ELEMENT_COLORS[info.element] !== undefined) {
|
|
22
|
+
color = ELEMENT_COLORS[info.element];
|
|
23
|
+
}
|
|
24
|
+
else if (crit) {
|
|
25
|
+
color = "#fbbf24";
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
color = "#fde68a";
|
|
29
|
+
}
|
|
30
|
+
const critScale = crit ? 1.55 : 1;
|
|
31
|
+
const scale = info.scale ?? 1;
|
|
32
|
+
const fontSizePx = Math.round(BASE_SIZE * critScale * scale);
|
|
33
|
+
return {
|
|
34
|
+
color,
|
|
35
|
+
fontSizePx,
|
|
36
|
+
fontWeight: crit ? 900 : 800,
|
|
37
|
+
glow: crit ? `0 0 10px ${color}` : "0 1px 3px rgba(0,0,0,0.95)",
|
|
38
|
+
};
|
|
39
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Game player shell for JGengine: React Three Fiber canvas, orbit camera, input tracking, HUD mounting, GameUiPreview, and a demo game. Consumers supply a GameRegistry.",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
@@ -15,6 +15,14 @@
|
|
|
15
15
|
"CHANGELOG.md"
|
|
16
16
|
],
|
|
17
17
|
"exports": {
|
|
18
|
+
"./weather": {
|
|
19
|
+
"types": "./dist/weather/index.d.ts",
|
|
20
|
+
"default": "./dist/weather/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./camera": {
|
|
23
|
+
"types": "./dist/camera/index.d.ts",
|
|
24
|
+
"default": "./dist/camera/index.js"
|
|
25
|
+
},
|
|
18
26
|
"./*": {
|
|
19
27
|
"types": "./dist/*.d.ts",
|
|
20
28
|
"default": "./dist/*.js"
|
|
@@ -26,9 +34,9 @@
|
|
|
26
34
|
"test": "bun test src"
|
|
27
35
|
},
|
|
28
36
|
"dependencies": {
|
|
29
|
-
"@jgengine/core": "^0.
|
|
30
|
-
"@jgengine/react": "^0.
|
|
31
|
-
"@jgengine/ws": "^0.
|
|
37
|
+
"@jgengine/core": "^0.7.0",
|
|
38
|
+
"@jgengine/react": "^0.7.0",
|
|
39
|
+
"@jgengine/ws": "^0.7.0"
|
|
32
40
|
},
|
|
33
41
|
"peerDependencies": {
|
|
34
42
|
"@react-three/drei": "^10.0.0",
|