@jgengine/shell 0.6.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +167 -2
  2. package/dist/GamePlayerShell.js +210 -39
  3. package/dist/audio/AudioComponents.d.ts +12 -0
  4. package/dist/audio/AudioComponents.js +53 -0
  5. package/dist/audio/audioEngine.d.ts +23 -0
  6. package/dist/audio/audioEngine.js +110 -0
  7. package/dist/camera/GameCameraRig.d.ts +11 -0
  8. package/dist/camera/GameCameraRig.js +42 -0
  9. package/dist/camera/GameOrbitCamera.d.ts +3 -1
  10. package/dist/camera/GameOrbitCamera.js +4 -2
  11. package/dist/camera/cameraRigs.d.ts +22 -0
  12. package/dist/camera/cameraRigs.js +485 -0
  13. package/dist/camera/index.d.ts +5 -0
  14. package/dist/camera/index.js +5 -0
  15. package/dist/camera/rigMath.d.ts +130 -0
  16. package/dist/camera/rigMath.js +338 -0
  17. package/dist/camera/shakeChannel.d.ts +29 -0
  18. package/dist/camera/shakeChannel.js +38 -0
  19. package/dist/demo/builderDemo.d.ts +2 -0
  20. package/dist/demo/builderDemo.js +189 -0
  21. package/dist/demo/mapDemo.d.ts +2 -0
  22. package/dist/demo/mapDemo.js +206 -0
  23. package/dist/demo/pointerDemo.d.ts +2 -0
  24. package/dist/demo/pointerDemo.js +151 -0
  25. package/dist/demo/sensorShowcase.d.ts +2 -0
  26. package/dist/demo/sensorShowcase.js +131 -0
  27. package/dist/demo/survivalDemo.d.ts +2 -0
  28. package/dist/demo/survivalDemo.js +291 -0
  29. package/dist/map/MapMarkerBeacons.d.ts +12 -0
  30. package/dist/map/MapMarkerBeacons.js +16 -0
  31. package/dist/map/index.d.ts +2 -0
  32. package/dist/map/index.js +2 -0
  33. package/dist/map/terrainMap.d.ts +23 -0
  34. package/dist/map/terrainMap.js +79 -0
  35. package/dist/pointer/PointerOverlays.d.ts +12 -0
  36. package/dist/pointer/PointerOverlays.js +17 -0
  37. package/dist/pointer/PointerProbe.d.ts +4 -0
  38. package/dist/pointer/PointerProbe.js +27 -0
  39. package/dist/pointer/pointerService.d.ts +24 -0
  40. package/dist/pointer/pointerService.js +68 -0
  41. package/dist/replay/useSessionRecorder.d.ts +14 -0
  42. package/dist/replay/useSessionRecorder.js +22 -0
  43. package/dist/structures/PlacementGhost.d.ts +8 -0
  44. package/dist/structures/PlacementGhost.js +11 -0
  45. package/dist/structures/index.d.ts +2 -0
  46. package/dist/structures/index.js +2 -0
  47. package/dist/structures.d.ts +1 -0
  48. package/dist/structures.js +1 -0
  49. package/dist/terrain/CarvedTerrain.d.ts +22 -0
  50. package/dist/terrain/CarvedTerrain.js +19 -0
  51. package/dist/terrain/EditableGround.d.ts +11 -0
  52. package/dist/terrain/EditableGround.js +38 -0
  53. package/dist/terrain/TerraformBrushCursor.d.ts +8 -0
  54. package/dist/terrain/TerraformBrushCursor.js +13 -0
  55. package/dist/terrain/index.d.ts +4 -1
  56. package/dist/terrain/index.js +4 -1
  57. package/dist/terrain/terrainMath.d.ts +14 -1
  58. package/dist/terrain/terrainMath.js +80 -0
  59. package/dist/vision/FrustumSensorHud.d.ts +21 -0
  60. package/dist/vision/FrustumSensorHud.js +67 -0
  61. package/dist/vision/HiddenStateProbeHud.d.ts +11 -0
  62. package/dist/vision/HiddenStateProbeHud.js +11 -0
  63. package/dist/vision/RevealVision.d.ts +28 -0
  64. package/dist/vision/RevealVision.js +53 -0
  65. package/dist/weather/FireSpreadLayer.d.ts +14 -0
  66. package/dist/weather/FireSpreadLayer.js +68 -0
  67. package/dist/weather/index.d.ts +1 -0
  68. package/dist/weather/index.js +1 -0
  69. package/dist/world/InstancedJoints.d.ts +14 -0
  70. package/dist/world/InstancedJoints.js +32 -0
  71. package/dist/world/WorldHud.d.ts +2 -0
  72. package/dist/world/WorldHud.js +70 -12
  73. package/dist/world/WorldItems.d.ts +5 -0
  74. package/dist/world/WorldItems.js +31 -0
  75. package/dist/world/floatTextStyle.d.ts +14 -0
  76. package/dist/world/floatTextStyle.js +39 -0
  77. package/package.json +8 -4
@@ -0,0 +1,14 @@
1
+ import * as THREE from "three";
2
+ import type { FireGrid } from "@jgengine/core/world/weather";
3
+ export interface FireSpreadLayerProps {
4
+ grid: FireGrid;
5
+ cellSize: number;
6
+ origin?: readonly [number, number];
7
+ /** Sample ground height so flames sit on terrain; defaults to y=0. */
8
+ heightAt?: (x: number, z: number) => number;
9
+ /** Flame quad height. Default 1.6. */
10
+ flameHeight?: number;
11
+ burningColor?: THREE.ColorRepresentation;
12
+ emberColor?: THREE.ColorRepresentation;
13
+ }
14
+ export declare function FireSpreadLayer({ grid, cellSize, origin, heightAt, flameHeight, burningColor, emberColor, }: FireSpreadLayerProps): import("react").JSX.Element;
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef } from "react";
3
+ import { useFrame } from "@react-three/fiber";
4
+ import * as THREE from "three";
5
+ const HIDDEN = new THREE.Matrix4().makeScale(0, 0, 0);
6
+ export function FireSpreadLayer({ grid, cellSize, origin = [0, 0], heightAt, flameHeight = 1.6, burningColor = "#ff6a1a", emberColor = "#4a1206", }) {
7
+ const flames = useRef(null);
8
+ const scorch = useRef(null);
9
+ const capacity = grid.cols * grid.rows;
10
+ const flameGeometry = useMemo(() => new THREE.PlaneGeometry(cellSize * 0.9, flameHeight), [cellSize, flameHeight]);
11
+ const scorchGeometry = useMemo(() => {
12
+ const geometry = new THREE.PlaneGeometry(cellSize, cellSize);
13
+ geometry.rotateX(-Math.PI / 2);
14
+ return geometry;
15
+ }, [cellSize]);
16
+ const flameMaterial = useMemo(() => new THREE.MeshBasicMaterial({ color: burningColor, transparent: true, opacity: 0.85, depthWrite: false, side: THREE.DoubleSide }), [burningColor]);
17
+ const scorchMaterial = useMemo(() => new THREE.MeshBasicMaterial({ color: emberColor, transparent: true, opacity: 0.7, depthWrite: false }), [emberColor]);
18
+ useEffect(() => () => {
19
+ flameGeometry.dispose();
20
+ scorchGeometry.dispose();
21
+ flameMaterial.dispose();
22
+ scorchMaterial.dispose();
23
+ }, [flameGeometry, scorchGeometry, flameMaterial, scorchMaterial]);
24
+ const matrix = useMemo(() => new THREE.Matrix4(), []);
25
+ const position = useMemo(() => new THREE.Vector3(), []);
26
+ const quaternion = useMemo(() => new THREE.Quaternion(), []);
27
+ const scale = useMemo(() => new THREE.Vector3(1, 1, 1), []);
28
+ useFrame((state) => {
29
+ const flameMesh = flames.current;
30
+ const scorchMesh = scorch.current;
31
+ if (flameMesh === null || scorchMesh === null)
32
+ return;
33
+ const cells = grid.snapshot();
34
+ const flicker = 0.85 + 0.15 * Math.sin(state.clock.elapsedTime * 12);
35
+ let flameIndex = 0;
36
+ let scorchIndex = 0;
37
+ for (let row = 0; row < grid.rows; row += 1) {
38
+ for (let col = 0; col < grid.cols; col += 1) {
39
+ const cell = cells[row * grid.cols + col];
40
+ const x = origin[0] + col * cellSize;
41
+ const z = origin[1] + row * cellSize;
42
+ const groundY = heightAt?.(x, z) ?? 0;
43
+ if (cell.state === "burning") {
44
+ position.set(x, groundY + flameHeight * 0.5, z);
45
+ quaternion.setFromEuler(new THREE.Euler(0, state.camera.rotation.y, 0));
46
+ scale.set(1, flicker, 1);
47
+ matrix.compose(position, quaternion, scale);
48
+ flameMesh.setMatrixAt(flameIndex, matrix);
49
+ flameIndex += 1;
50
+ }
51
+ else if (cell.state === "burnt") {
52
+ position.set(x, groundY + 0.05, z);
53
+ scorchMesh.setMatrixAt(scorchIndex, matrix.compose(position, new THREE.Quaternion(), scale.set(1, 1, 1)));
54
+ scorchIndex += 1;
55
+ }
56
+ }
57
+ }
58
+ for (let i = flameIndex; i < capacity; i += 1)
59
+ flameMesh.setMatrixAt(i, HIDDEN);
60
+ for (let i = scorchIndex; i < capacity; i += 1)
61
+ scorchMesh.setMatrixAt(i, HIDDEN);
62
+ flameMesh.count = capacity;
63
+ scorchMesh.count = capacity;
64
+ flameMesh.instanceMatrix.needsUpdate = true;
65
+ scorchMesh.instanceMatrix.needsUpdate = true;
66
+ });
67
+ return (_jsxs("group", { children: [_jsx("instancedMesh", { ref: scorch, args: [scorchGeometry, scorchMaterial, capacity], frustumCulled: false, renderOrder: 5 }), _jsx("instancedMesh", { ref: flames, args: [flameGeometry, flameMaterial, capacity], frustumCulled: false, renderOrder: 11 })] }));
68
+ }
@@ -1,3 +1,4 @@
1
+ export { FireSpreadLayer, type FireSpreadLayerProps } from "./FireSpreadLayer.js";
1
2
  export { LightningStrike, type LightningStrikeProps } from "./LightningStrike.js";
2
3
  export { RainField, type RainFieldProps } from "./RainField.js";
3
4
  export { SnowField, type SnowFieldProps } from "./SnowField.js";
@@ -1,3 +1,4 @@
1
+ export { FireSpreadLayer } from "./FireSpreadLayer.js";
1
2
  export { LightningStrike } from "./LightningStrike.js";
2
3
  export { RainField } from "./RainField.js";
3
4
  export { SnowField } from "./SnowField.js";
@@ -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
+ }
@@ -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;
@@ -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 { useEffect, useRef, useState } from "react";
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({ text, kind, lifeMs }) {
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", { className: kind === "heal" ? "text-emerald-300" : "text-amber-200", style: {
31
+ return (_jsx("span", { style: {
29
32
  display: "inline-block",
30
- fontWeight: 800,
31
- fontSize: "18px",
32
- textShadow: "0 1px 3px rgba(0,0,0,0.95)",
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, { text: floater.text, kind: floater.kind, lifeMs: lifeMs }) }, floater.id))) }));
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.6.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",
@@ -19,6 +19,10 @@
19
19
  "types": "./dist/weather/index.d.ts",
20
20
  "default": "./dist/weather/index.js"
21
21
  },
22
+ "./camera": {
23
+ "types": "./dist/camera/index.d.ts",
24
+ "default": "./dist/camera/index.js"
25
+ },
22
26
  "./*": {
23
27
  "types": "./dist/*.d.ts",
24
28
  "default": "./dist/*.js"
@@ -30,9 +34,9 @@
30
34
  "test": "bun test src"
31
35
  },
32
36
  "dependencies": {
33
- "@jgengine/core": "^0.6.0",
34
- "@jgengine/react": "^0.6.0",
35
- "@jgengine/ws": "^0.6.0"
37
+ "@jgengine/core": "^0.7.0",
38
+ "@jgengine/react": "^0.7.0",
39
+ "@jgengine/ws": "^0.7.0"
36
40
  },
37
41
  "peerDependencies": {
38
42
  "@react-three/drei": "^10.0.0",