@jgengine/shell 0.3.0 → 0.5.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 +42 -0
- package/dist/GamePlayerShell.js +43 -13
- package/dist/camera/GameFirstPersonCamera.d.ts +9 -0
- package/dist/camera/GameFirstPersonCamera.js +63 -0
- package/dist/camera/GameOrbitCamera.d.ts +3 -1
- package/dist/camera/GameOrbitCamera.js +6 -3
- package/dist/camera/index.d.ts +1 -1
- package/dist/camera/index.js +1 -1
- package/dist/camera/orbitCameraMath.d.ts +7 -0
- package/dist/camera/orbitCameraMath.js +7 -0
- package/dist/world/WorldHud.d.ts +14 -0
- package/dist/world/WorldHud.js +78 -0
- package/package.json +10 -7
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All eight `@jgengine/*` packages are versioned in lockstep, so this one file
|
|
4
|
+
covers every release. Format follows [Keep a Changelog](https://keepachangelog.com);
|
|
5
|
+
each release **leads with a Migrate block** — the concrete steps to move a game
|
|
6
|
+
from the previous version onto the new APIs — because the point of a bump is to
|
|
7
|
+
let consumers pick up the better stuff, not just to list what moved.
|
|
8
|
+
|
|
9
|
+
Agents building on the published SDK can also read this programmatically:
|
|
10
|
+
`import { VERSION, CHANGELOG } from "@jgengine/core/meta/changelog"` gives the
|
|
11
|
+
same data as typed values, so an updater can diff its installed version against
|
|
12
|
+
the latest and surface the migration steps.
|
|
13
|
+
|
|
14
|
+
## Unreleased
|
|
15
|
+
|
|
16
|
+
_Nothing yet._
|
|
17
|
+
|
|
18
|
+
## 0.5.0
|
|
19
|
+
|
|
20
|
+
An additive release: **every 0.4.0 API is unchanged**, so upgrading is only a
|
|
21
|
+
version bump. New pure primitives across progression, inventory slots, world
|
|
22
|
+
geometry, and React store bindings.
|
|
23
|
+
|
|
24
|
+
### Migrate
|
|
25
|
+
|
|
26
|
+
- Bump every `@jgengine/*` dependency to `^0.5.0` (the eight packages version in lockstep).
|
|
27
|
+
- No code change is required — 0.5.0 adds surface, it doesn't move or remove any.
|
|
28
|
+
- Optional: replace a game's hand-rolled `progression/curves.ts` with the new `leveling(...)` track. `leveling({ xpForLevel: { kind: "power", base, exponent, round: "floor" }, maxLevel })` returns `xpForLevel`, `resolve`, and `grantXp(ctx.scene.entity.stats, userId, amount, onLevelUp?)` — a drop-in for the old `xpRequiredForLevel` / `resolveLevelProgress` / `grantXp` exports. `ctx.scene.entity.stats` satisfies the primitive's `LevelingStatAccess` structurally, so no adapter is needed.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- `@jgengine/core/game/progression` — genre-agnostic progression primitive. `curve(spec)` / `evalCurve(spec, x)` evaluate declarative scalar curves (`const`, `linear`, `power`, `geometric`, `steps`, `piecewise`, each with optional `round`/`min`/`max`) for speed-by-level, difficulty-by-wave, loot drop-rate ramps, and similar scaling. `leveling(config)` builds the stateful XP→level track (threshold accumulation, multi-level grants, cap handling, `stat.levelUp` emit) on top of an `xpForLevel` curve.
|
|
33
|
+
- `@jgengine/core/inventory/slotModel` — pure slot-grid primitives (`createSlots`, `placeAt`, `removeAt`, `moveSlot`).
|
|
34
|
+
- `@jgengine/core/world/geometry`, `/world/interiors`, `/world/placement` — pure world primitives: grid snapping, footprint AABBs and overlap, interior/exterior spaces, and placement validation.
|
|
35
|
+
- `@jgengine/react/engineStore` — raw-store React bindings (`useEngineState`, `useEngineStore`, `useEngineEvent`).
|
|
36
|
+
- Pure/functional tiers for the `trade`, `unlocks`, `quest`, and `feed` verbs in `@jgengine/core/game`.
|
|
37
|
+
|
|
38
|
+
## 0.4.0
|
|
39
|
+
|
|
40
|
+
Baseline release: the eight `@jgengine/*` packages (core, ws, sql, react,
|
|
41
|
+
convex, node, shell, assets) as the first tracked version. No migration —
|
|
42
|
+
this is the floor changelog entries are measured against.
|
package/dist/GamePlayerShell.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Canvas, useFrame, useLoader } from "@react-three/fiber";
|
|
3
3
|
import { Component, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import * as THREE from "three";
|
|
5
|
+
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
5
6
|
import { createActionStateTracker, hotbarSlotActionIndex, resolveActionCommand, toActionStateBindingMap, } from "@jgengine/core/input/actionBindings";
|
|
6
7
|
import { resolveActivePrompt } from "@jgengine/core/interaction/proximityPrompt";
|
|
7
8
|
import { advancePlayerMotion, createEmptyMovementKeys, createPlayerMotionState, resolveMovementIntent, } from "@jgengine/core/movement/movementModel";
|
|
@@ -10,6 +11,8 @@ import { useGameContext } from "@jgengine/react/provider";
|
|
|
10
11
|
import { useSceneEntities, useSceneObjects, usePlayer, useTarget } from "@jgengine/react/hooks";
|
|
11
12
|
import { GameProvider } from "@jgengine/react/provider";
|
|
12
13
|
import { GAME_SIM_FRAME_PRIORITY, GameOrbitCamera } from "./camera/index.js";
|
|
14
|
+
import { GameFirstPersonCamera } from "./camera/GameFirstPersonCamera.js";
|
|
15
|
+
import { ProjectileTracers, Reticle, WorldEntityBars, WorldFloatText } from "./world/WorldHud.js";
|
|
13
16
|
const DEV_USER_ID = "dev-player";
|
|
14
17
|
const TURN_SPEED = 2.4;
|
|
15
18
|
const PRIMARY_CLICK_MOVE_THRESHOLD_PX = 6;
|
|
@@ -51,7 +54,7 @@ function hotbarIdFor(playable) {
|
|
|
51
54
|
const hud = declarations.find(([, declaration]) => declaration.hud === "hotbar");
|
|
52
55
|
return (hud ?? declarations[0])?.[0] ?? null;
|
|
53
56
|
}
|
|
54
|
-
function executeHotbarSlot(ctx, hotbarId, slot, yaw) {
|
|
57
|
+
function executeHotbarSlot(ctx, hotbarId, slot, yaw, pitch) {
|
|
55
58
|
const stack = ctx.player.inventory.state(hotbarId).slots[slot];
|
|
56
59
|
if (stack === undefined || stack === null)
|
|
57
60
|
return { ok: false, error: `Hotbar slot ${slot + 1} is empty` };
|
|
@@ -59,7 +62,7 @@ function executeHotbarSlot(ctx, hotbarId, slot, yaw) {
|
|
|
59
62
|
from: ctx.player.userId,
|
|
60
63
|
itemId: stack.itemId,
|
|
61
64
|
inventoryId: hotbarId,
|
|
62
|
-
aim: { yaw, pitch
|
|
65
|
+
aim: { yaw, pitch },
|
|
63
66
|
});
|
|
64
67
|
return result.error === undefined ? { ok: true } : { ok: false, error: result.error };
|
|
65
68
|
}
|
|
@@ -78,13 +81,27 @@ function EntitySprite({ sprite }) {
|
|
|
78
81
|
}, [texture]);
|
|
79
82
|
return (_jsx("sprite", { "position-y": sprite.y, scale: [sprite.width, sprite.height, 1], children: _jsx("spriteMaterial", { map: texture, transparent: true, alphaTest: 0.08, depthWrite: false }) }));
|
|
80
83
|
}
|
|
81
|
-
function
|
|
84
|
+
function EntityModel({ model }) {
|
|
85
|
+
const gltf = useLoader(GLTFLoader, model.url);
|
|
86
|
+
const scene = useMemo(() => gltf.scene.clone(true), [gltf]);
|
|
87
|
+
const scale = model.scale ?? 1;
|
|
88
|
+
return _jsx("primitive", { object: scene, "position-y": model.y ?? 0, scale: [scale, scale, scale] });
|
|
89
|
+
}
|
|
90
|
+
function resolveModel(value, assets) {
|
|
91
|
+
if (value === undefined)
|
|
92
|
+
return undefined;
|
|
93
|
+
if (typeof value !== "string")
|
|
94
|
+
return value;
|
|
95
|
+
const url = assets.resolve(value)?.url;
|
|
96
|
+
return url === undefined ? undefined : { url };
|
|
97
|
+
}
|
|
98
|
+
function EntityMarker({ entity, model, sprite, isLocal, targeted, onSelect, }) {
|
|
82
99
|
const color = isLocal ? "#4ade80" : entity.role === "npc" ? colorFromId(entity.name) : "#9ca3af";
|
|
83
100
|
return (_jsxs("group", { position: [entity.position[0], entity.position[1], entity.position[2]], "rotation-y": entity.rotationY, onPointerDown: (event) => {
|
|
84
101
|
event.stopPropagation();
|
|
85
102
|
if (!isLocal)
|
|
86
103
|
onSelect(entity);
|
|
87
|
-
}, children: [sprite !== undefined ? (_jsx(EntitySprite, { sprite: sprite })) : entity.role === "prop" ? (_jsxs("mesh", { "position-y": 0.5, children: [_jsx("sphereGeometry", { args: [0.45, 16, 16] }), _jsx("meshStandardMaterial", { color: color })] })) : (_jsxs(_Fragment, { children: [_jsxs("mesh", { "position-y": 0.95, children: [_jsx("capsuleGeometry", { args: [0.35, 1.1, 6, 14] }), _jsx("meshStandardMaterial", { color: color })] }), _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] }));
|
|
104
|
+
}, children: [model !== undefined ? (_jsx(EntityModel, { model: model })) : sprite !== undefined ? (_jsx(EntitySprite, { sprite: sprite })) : entity.role === "prop" ? (_jsxs("mesh", { "position-y": 0.5, children: [_jsx("sphereGeometry", { args: [0.45, 16, 16] }), _jsx("meshStandardMaterial", { color: color })] })) : (_jsxs(_Fragment, { children: [_jsxs("mesh", { "position-y": 0.95, children: [_jsx("capsuleGeometry", { args: [0.35, 1.1, 6, 14] }), _jsx("meshStandardMaterial", { color: color })] }), _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] }));
|
|
88
105
|
}
|
|
89
106
|
function GroundPlane() {
|
|
90
107
|
const geometry = useMemo(() => {
|
|
@@ -117,7 +134,7 @@ function RockField() {
|
|
|
117
134
|
}), []);
|
|
118
135
|
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], children: [_jsx("dodecahedronGeometry", { args: [0.8, 0] }), _jsx("meshStandardMaterial", { color: "#6b6f63", roughness: 1 })] }, rock.id))) }));
|
|
119
136
|
}
|
|
120
|
-
function WorldView({ entitySprites }) {
|
|
137
|
+
function WorldView({ entitySprites, entityModels, objectModels, assets, }) {
|
|
121
138
|
const ctx = useGameContext();
|
|
122
139
|
const entities = useSceneEntities();
|
|
123
140
|
const objects = useSceneObjects();
|
|
@@ -127,12 +144,16 @@ function WorldView({ entitySprites }) {
|
|
|
127
144
|
const relation = ctx.scene.entity.canReceive(entity.id, "damage") === null ? "hostile" : "friendly";
|
|
128
145
|
ctx.scene.entity.setTarget(player.userId, relation === "hostile" || entity.role === "npc" ? entity.id : null);
|
|
129
146
|
};
|
|
130
|
-
return (_jsxs(_Fragment, { children: [_jsx(GroundPlane, {}), _jsx("gridHelper", { args: [160, 80, "#3a3f4a", "#2b2f38"], "position-y": 0.01 }), _jsx(RockField, {}), entities.map((entity) => (_jsx(EntityMarker, { entity: entity, sprite: entitySprites?.[entity.name], isLocal: entity.id === player.userId, targeted: entity.id === targetId, onSelect: handleSelect }, entity.id))), objects.map((object) =>
|
|
147
|
+
return (_jsxs(_Fragment, { children: [_jsx(GroundPlane, {}), _jsx("gridHelper", { args: [160, 80, "#3a3f4a", "#2b2f38"], "position-y": 0.01 }), _jsx(RockField, {}), entities.map((entity) => (_jsx(EntityMarker, { entity: entity, model: resolveModel(entityModels?.[entity.name], assets), sprite: entitySprites?.[entity.name], isLocal: entity.id === player.userId, targeted: entity.id === targetId, onSelect: handleSelect }, entity.id))), objects.map((object) => {
|
|
148
|
+
const model = resolveModel(objectModels?.[object.catalogId], assets) ??
|
|
149
|
+
resolveModel(object.catalogId, assets);
|
|
150
|
+
return (_jsx("group", { position: [object.position[0], object.position[1], object.position[2]], "rotation-y": object.rotationY, children: model !== undefined ? (_jsx(EntityModel, { model: model })) : (_jsxs("mesh", { "position-y": 0.5, children: [_jsx("boxGeometry", { args: [1, 1, 1] }), _jsx("meshStandardMaterial", { color: colorFromId(object.catalogId) })] })) }, object.instanceId));
|
|
151
|
+
})] }));
|
|
131
152
|
}
|
|
132
153
|
function RemotePlayers({ rows }) {
|
|
133
154
|
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))) }));
|
|
134
155
|
}
|
|
135
|
-
function FrameDriver({ ctx, playable, tracker, yawRef, primaryClickRef, onRuntimeError, multiplayer, serverIdRef, }) {
|
|
156
|
+
function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryClickRef, onRuntimeError, multiplayer, serverIdRef, }) {
|
|
136
157
|
const motionRef = useRef(createPlayerMotionState());
|
|
137
158
|
const hasReportedTickError = useRef(false);
|
|
138
159
|
const slotActions = useMemo(() => findHotbarSlotActions(playable.game.input), [playable]);
|
|
@@ -201,7 +222,7 @@ function FrameDriver({ ctx, playable, tracker, yawRef, primaryClickRef, onRuntim
|
|
|
201
222
|
for (const { action, slot } of slotActions) {
|
|
202
223
|
if (!tracker.wasPressed(action))
|
|
203
224
|
continue;
|
|
204
|
-
const result = executeHotbarSlot(ctx, hotbarId, slot, yawRef.current);
|
|
225
|
+
const result = executeHotbarSlot(ctx, hotbarId, slot, yawRef.current, pitchRef.current);
|
|
205
226
|
if (!result.ok)
|
|
206
227
|
console.warn(`[jgengine:item-use] ${result.error}`);
|
|
207
228
|
}
|
|
@@ -214,7 +235,7 @@ function FrameDriver({ ctx, playable, tracker, yawRef, primaryClickRef, onRuntim
|
|
|
214
235
|
? preferred
|
|
215
236
|
: slots.findIndex((stack) => stack !== null);
|
|
216
237
|
if (slot >= 0) {
|
|
217
|
-
const result = executeHotbarSlot(ctx, hotbarId, slot, yawRef.current);
|
|
238
|
+
const result = executeHotbarSlot(ctx, hotbarId, slot, yawRef.current, pitchRef.current);
|
|
218
239
|
if (!result.ok)
|
|
219
240
|
console.warn(`[jgengine:item-use] ${result.error}`);
|
|
220
241
|
}
|
|
@@ -230,7 +251,7 @@ function FrameDriver({ ctx, playable, tracker, yawRef, primaryClickRef, onRuntim
|
|
|
230
251
|
y: focus.position[1],
|
|
231
252
|
z: focus.position[2],
|
|
232
253
|
rotationY: focus.rotationY,
|
|
233
|
-
rotationPitch:
|
|
254
|
+
rotationPitch: pitchRef.current,
|
|
234
255
|
});
|
|
235
256
|
}
|
|
236
257
|
}
|
|
@@ -270,6 +291,7 @@ export function GamePlayerShell({ playable, multiplayer = null, }) {
|
|
|
270
291
|
const [remotePlayers, setRemotePlayers] = useState([]);
|
|
271
292
|
const wrapperRef = useRef(null);
|
|
272
293
|
const yawRef = useRef(0);
|
|
294
|
+
const pitchRef = useRef(0);
|
|
273
295
|
const serverIdRef = useRef(null);
|
|
274
296
|
const cameraDraggingRef = useRef(false);
|
|
275
297
|
const primaryClickRef = useRef(false);
|
|
@@ -370,6 +392,14 @@ export function GamePlayerShell({ playable, multiplayer = null, }) {
|
|
|
370
392
|
return _jsx("div", { className: "h-full w-full bg-neutral-950" });
|
|
371
393
|
const GameUI = playable.GameUI;
|
|
372
394
|
const WorldOverlay = playable.WorldOverlay;
|
|
395
|
+
const firstPerson = playable.camera?.perspective === "first";
|
|
396
|
+
const showReticle = firstPerson && playable.camera?.firstPerson?.reticle !== false;
|
|
397
|
+
const worldBars = playable.worldHealthBars;
|
|
398
|
+
const barsStatId = worldBars === undefined || worldBars === false
|
|
399
|
+
? null
|
|
400
|
+
: worldBars === true
|
|
401
|
+
? "health"
|
|
402
|
+
: worldBars.statId ?? "health";
|
|
373
403
|
return (_jsxs("div", { ref: wrapperRef, tabIndex: 0, className: "relative h-full w-full bg-neutral-950 outline-none", onKeyDown: (event) => {
|
|
374
404
|
if (event.code === "Tab" || event.code === "Space")
|
|
375
405
|
event.preventDefault();
|
|
@@ -400,7 +430,7 @@ export function GamePlayerShell({ playable, multiplayer = null, }) {
|
|
|
400
430
|
else if (event.deltaY > 0 && ctx.game.commands.has("ui.hotbarScrollPrev")) {
|
|
401
431
|
ctx.game.commands.run("ui.hotbarScrollPrev", {});
|
|
402
432
|
}
|
|
403
|
-
}, children: [_jsxs(Canvas, { camera: { fov: 55, near: 0.1, far: 300 }, children: [_jsx("color", { attach: "background", args: ["#14161b"] }), _jsx("ambientLight", { intensity: 0.55 }), _jsx("directionalLight", { position: [10, 16, 6], intensity: 1.3 }), _jsxs(GameProvider, { context: ctx, children: [_jsx(WorldView, { entitySprites: playable.entitySprites }), WorldOverlay !== undefined ? _jsx(WorldOverlay, {}) : null, _jsx(GameOrbitCamera, { yawRef: yawRef, config: playable.camera, followEntityId: playable.camera?.followEntityId, onCameraFollow: playable.camera?.onCameraFollow, onDragChange: (dragging) => {
|
|
433
|
+
}, children: [_jsxs(Canvas, { camera: { fov: 55, near: 0.1, far: 300 }, children: [_jsx("color", { attach: "background", args: ["#14161b"] }), _jsx("ambientLight", { intensity: 0.55 }), _jsx("directionalLight", { position: [10, 16, 6], intensity: 1.3 }), _jsxs(GameProvider, { context: ctx, children: [_jsx(WorldView, { entitySprites: playable.entitySprites, entityModels: playable.entityModels, objectModels: playable.objectModels, assets: playable.game.assets }), WorldOverlay !== undefined ? _jsx(WorldOverlay, {}) : null, barsStatId !== null ? _jsx(WorldEntityBars, { statId: barsStatId }) : null, _jsx(WorldFloatText, {}), _jsx(ProjectileTracers, {}), firstPerson ? (_jsx(GameFirstPersonCamera, { yawRef: yawRef, pitchRef: pitchRef, config: playable.camera?.firstPerson, followEntityId: playable.camera?.followEntityId })) : (_jsx(GameOrbitCamera, { yawRef: yawRef, pitchRef: pitchRef, config: playable.camera, followEntityId: playable.camera?.followEntityId, onCameraFollow: playable.camera?.onCameraFollow, onDragChange: (dragging) => {
|
|
404
434
|
cameraDraggingRef.current = dragging;
|
|
405
|
-
} })] }), _jsx(RemotePlayers, { rows: remotePlayers }), _jsx(FrameDriver, { ctx: ctx, playable: playable, tracker: tracker, yawRef: yawRef, primaryClickRef: primaryClickRef, onRuntimeError: reportRuntimeError, multiplayer: multiplayer, serverIdRef: serverIdRef })] }), _jsx(GameUiErrorBoundary, { onRuntimeError: reportRuntimeError, children: _jsx(GameProvider, { context: ctx, children: _jsx(GameUI, {}) }) }), _jsx(DiagnosticOverlay, { diagnostics: diagnostics })] }));
|
|
435
|
+
} }))] }), _jsx(RemotePlayers, { rows: remotePlayers }), _jsx(FrameDriver, { ctx: ctx, playable: playable, tracker: tracker, yawRef: yawRef, pitchRef: pitchRef, primaryClickRef: primaryClickRef, onRuntimeError: reportRuntimeError, multiplayer: multiplayer, serverIdRef: serverIdRef })] }), _jsx(GameUiErrorBoundary, { onRuntimeError: reportRuntimeError, children: _jsx(GameProvider, { context: ctx, children: _jsx(GameUI, {}) }) }), showReticle ? _jsx(Reticle, {}) : null, _jsx(DiagnosticOverlay, { diagnostics: diagnostics })] }));
|
|
406
436
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type MutableRefObject } from "react";
|
|
2
|
+
import type { FirstPersonCameraConfig } from "@jgengine/core/game/playableGame";
|
|
3
|
+
export interface GameFirstPersonCameraProps {
|
|
4
|
+
yawRef: MutableRefObject<number>;
|
|
5
|
+
pitchRef: MutableRefObject<number>;
|
|
6
|
+
config?: FirstPersonCameraConfig;
|
|
7
|
+
followEntityId?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function GameFirstPersonCamera({ yawRef, pitchRef, config, followEntityId, }: GameFirstPersonCameraProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useFrame, useThree } from "@react-three/fiber";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import * as THREE from "three";
|
|
5
|
+
import { useGameContext } from "@jgengine/react/provider";
|
|
6
|
+
import { usePlayer } from "@jgengine/react/hooks";
|
|
7
|
+
import { GAME_SIM_FRAME_PRIORITY, ORBIT_CAMERA_FRAME_PRIORITY } from "./orbitCameraMath.js";
|
|
8
|
+
const DEFAULT_EYE_HEIGHT = 1.6;
|
|
9
|
+
const DEFAULT_SENSITIVITY = 0.0025;
|
|
10
|
+
const DEFAULT_MAX_PITCH = 1.45;
|
|
11
|
+
export function GameFirstPersonCamera({ yawRef, pitchRef, config, followEntityId, }) {
|
|
12
|
+
const eyeHeight = config?.eyeHeight ?? DEFAULT_EYE_HEIGHT;
|
|
13
|
+
const sensitivity = config?.sensitivity ?? DEFAULT_SENSITIVITY;
|
|
14
|
+
const maxPitch = config?.maxPitch ?? DEFAULT_MAX_PITCH;
|
|
15
|
+
const { userId } = usePlayer();
|
|
16
|
+
const ctx = useGameContext();
|
|
17
|
+
const camera = useThree((state) => state.camera);
|
|
18
|
+
const domElement = useThree((state) => state.gl.domElement);
|
|
19
|
+
const followId = followEntityId ?? userId;
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const requestLock = () => {
|
|
22
|
+
if (document.pointerLockElement !== domElement)
|
|
23
|
+
void domElement.requestPointerLock?.();
|
|
24
|
+
};
|
|
25
|
+
const onMove = (event) => {
|
|
26
|
+
if (document.pointerLockElement !== domElement)
|
|
27
|
+
return;
|
|
28
|
+
yawRef.current -= event.movementX * sensitivity;
|
|
29
|
+
pitchRef.current = Math.max(-maxPitch, Math.min(maxPitch, pitchRef.current - event.movementY * sensitivity));
|
|
30
|
+
};
|
|
31
|
+
domElement.addEventListener("click", requestLock);
|
|
32
|
+
window.addEventListener("mousemove", onMove);
|
|
33
|
+
return () => {
|
|
34
|
+
domElement.removeEventListener("click", requestLock);
|
|
35
|
+
window.removeEventListener("mousemove", onMove);
|
|
36
|
+
};
|
|
37
|
+
}, [domElement, sensitivity, maxPitch, yawRef, pitchRef]);
|
|
38
|
+
useFrame(() => {
|
|
39
|
+
const entity = ctx.scene.entity.get(followId);
|
|
40
|
+
if (entity === null)
|
|
41
|
+
return;
|
|
42
|
+
const cosPitch = Math.cos(pitchRef.current);
|
|
43
|
+
camera.position.set(entity.position[0], entity.position[1] + eyeHeight, entity.position[2]);
|
|
44
|
+
camera.lookAt(camera.position.x + Math.sin(yawRef.current) * cosPitch, camera.position.y + Math.sin(pitchRef.current), camera.position.z + Math.cos(yawRef.current) * cosPitch);
|
|
45
|
+
}, ORBIT_CAMERA_FRAME_PRIORITY);
|
|
46
|
+
if (config?.viewmodel === false)
|
|
47
|
+
return null;
|
|
48
|
+
return _jsx(FirstPersonViewmodel, { camera: camera });
|
|
49
|
+
}
|
|
50
|
+
function FirstPersonViewmodel({ camera }) {
|
|
51
|
+
const groupRef = useRef(null);
|
|
52
|
+
useFrame(() => {
|
|
53
|
+
const group = groupRef.current;
|
|
54
|
+
if (group === null)
|
|
55
|
+
return;
|
|
56
|
+
group.position.copy(camera.position);
|
|
57
|
+
group.quaternion.copy(camera.quaternion);
|
|
58
|
+
group.translateX(0.34);
|
|
59
|
+
group.translateY(-0.26);
|
|
60
|
+
group.translateZ(-0.72);
|
|
61
|
+
}, GAME_SIM_FRAME_PRIORITY);
|
|
62
|
+
return (_jsxs("group", { ref: groupRef, children: [_jsxs("mesh", { position: [0, 0, -0.22], children: [_jsx("boxGeometry", { args: [0.09, 0.11, 0.55] }), _jsx("meshStandardMaterial", { color: "#22262d", metalness: 0.6, roughness: 0.35 })] }), _jsxs("mesh", { position: [0, -0.13, 0.04], rotation: [0.35, 0, 0], children: [_jsx("boxGeometry", { args: [0.08, 0.18, 0.11] }), _jsx("meshStandardMaterial", { color: "#33373f", metalness: 0.4, roughness: 0.5 })] }), _jsxs("mesh", { position: [0, 0.03, -0.52], rotation: [Math.PI / 2, 0, 0], children: [_jsx("cylinderGeometry", { args: [0.022, 0.022, 0.18, 10] }), _jsx("meshStandardMaterial", { color: "#0e0f12", metalness: 0.7, roughness: 0.3 })] })] }));
|
|
63
|
+
}
|
|
@@ -5,6 +5,8 @@ import { type CameraFollowState, type OrbitCameraConfig, type Vec3 } from "./orb
|
|
|
5
5
|
export type CameraFollowListener = (state: CameraFollowState) => void;
|
|
6
6
|
export interface GameOrbitCameraProps {
|
|
7
7
|
yawRef: MutableRefObject<number>;
|
|
8
|
+
/** Written each frame with the camera's look elevation (radians) for aim.pitch. */
|
|
9
|
+
pitchRef?: MutableRefObject<number>;
|
|
8
10
|
config?: Partial<OrbitCameraConfig>;
|
|
9
11
|
followEntityId?: string;
|
|
10
12
|
/** Override orbit target derived from the followed entity. */
|
|
@@ -12,6 +14,6 @@ export interface GameOrbitCameraProps {
|
|
|
12
14
|
onDragChange?: (dragging: boolean) => void;
|
|
13
15
|
onCameraFollow?: CameraFollowListener;
|
|
14
16
|
}
|
|
15
|
-
export declare function GameOrbitCamera({ yawRef, config: configPatch, followEntityId, resolveFollowTarget, onDragChange, onCameraFollow, }: GameOrbitCameraProps): import("react").JSX.Element;
|
|
17
|
+
export declare function GameOrbitCamera({ yawRef, pitchRef, config: configPatch, followEntityId, resolveFollowTarget, onDragChange, onCameraFollow, }: GameOrbitCameraProps): import("react").JSX.Element;
|
|
16
18
|
/** Seed orbit target before controls mount (demo spawn at origin). */
|
|
17
19
|
export declare function seedOrbitCameraTarget(camera: Camera, target: Vector3, distance: number, height: number): void;
|
|
@@ -5,8 +5,8 @@ import { useEffect, useRef } from "react";
|
|
|
5
5
|
import { MOUSE, Vector3 } from "three";
|
|
6
6
|
import { useGameContext } from "@jgengine/react/provider";
|
|
7
7
|
import { usePlayer } from "@jgengine/react/hooks";
|
|
8
|
-
import { ORBIT_CAMERA_FRAME_PRIORITY, orbitFollowStep, orbitYawFromCamera, resolveFollowTargetFromPosition, resolveOrbitCameraConfig, seedOrbitFollowState, } from "./orbitCameraMath.js";
|
|
9
|
-
export function GameOrbitCamera({ yawRef, config: configPatch, followEntityId, resolveFollowTarget, onDragChange, onCameraFollow, }) {
|
|
8
|
+
import { cameraLookPitch, ORBIT_CAMERA_FRAME_PRIORITY, orbitFollowStep, orbitYawFromCamera, resolveFollowTargetFromPosition, resolveOrbitCameraConfig, seedOrbitFollowState, } from "./orbitCameraMath.js";
|
|
9
|
+
export function GameOrbitCamera({ yawRef, pitchRef, config: configPatch, followEntityId, resolveFollowTarget, onDragChange, onCameraFollow, }) {
|
|
10
10
|
const config = resolveOrbitCameraConfig(configPatch);
|
|
11
11
|
const controlsRef = useRef(null);
|
|
12
12
|
const runtimeRef = useRef(null);
|
|
@@ -61,6 +61,9 @@ export function GameOrbitCamera({ yawRef, config: configPatch, followEntityId, r
|
|
|
61
61
|
};
|
|
62
62
|
const [px, , pz] = entity.position;
|
|
63
63
|
yawRef.current = orbitYawFromCamera(camera.position.x, camera.position.z, px, pz);
|
|
64
|
+
if (pitchRef !== undefined) {
|
|
65
|
+
pitchRef.current = cameraLookPitch({ x: camera.position.x, y: camera.position.y, z: camera.position.z }, stepped.target);
|
|
66
|
+
}
|
|
64
67
|
onCameraFollow?.({
|
|
65
68
|
entityId: followId,
|
|
66
69
|
target: stepped.target,
|
|
@@ -68,7 +71,7 @@ export function GameOrbitCamera({ yawRef, config: configPatch, followEntityId, r
|
|
|
68
71
|
distance: runtimeRef.current.lockedDistance ?? stepped.distance,
|
|
69
72
|
});
|
|
70
73
|
}, ORBIT_CAMERA_FRAME_PRIORITY);
|
|
71
|
-
return (_jsx(OrbitControls, { ref: controlsRef, enableDamping: true, dampingFactor: config.dampingFactor, rotateSpeed: config.rotateSpeed, zoomSpeed: config.zoomSpeed, enablePan: false, enableRotate: true, enableZoom: true, minDistance: config.minDistance, maxDistance: config.maxDistance, maxPolarAngle:
|
|
74
|
+
return (_jsx(OrbitControls, { ref: controlsRef, enableDamping: true, dampingFactor: config.dampingFactor, rotateSpeed: config.rotateSpeed, zoomSpeed: config.zoomSpeed, enablePan: false, enableRotate: true, enableZoom: true, minDistance: config.minDistance, maxDistance: config.maxDistance, maxPolarAngle: config.maxPolarAngle, minPolarAngle: config.minPolarAngle, screenSpacePanning: false, mouseButtons: { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: undefined }, onStart: () => {
|
|
72
75
|
draggingRef.current = true;
|
|
73
76
|
onDragChange?.(true);
|
|
74
77
|
}, onEnd: () => {
|
package/dist/camera/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { GameOrbitCamera, type CameraFollowListener, type GameOrbitCameraProps } from "./GameOrbitCamera.js";
|
|
2
|
-
export { cameraFollowStep, DEFAULT_ORBIT_CAMERA, distanceBetween, GAME_SIM_FRAME_PRIORITY, lerpVec3, ORBIT_CAMERA_FRAME_PRIORITY, orbitFollowStep, orbitYawFromCamera, resolveFollowTargetFromPosition, resolveOrbitCameraConfig, resolveTargetSmoothing, seedOrbitFollowState, smoothBlend, type CameraFollowState, type OrbitCameraConfig, type OrbitFollowRuntimeState, type ResolvedOrbitCameraConfig, type Vec3, } from "./orbitCameraMath.js";
|
|
2
|
+
export { cameraFollowStep, cameraLookPitch, DEFAULT_ORBIT_CAMERA, distanceBetween, GAME_SIM_FRAME_PRIORITY, lerpVec3, ORBIT_CAMERA_FRAME_PRIORITY, orbitFollowStep, orbitYawFromCamera, resolveFollowTargetFromPosition, resolveOrbitCameraConfig, resolveTargetSmoothing, seedOrbitFollowState, smoothBlend, type CameraFollowState, type OrbitCameraConfig, type OrbitFollowRuntimeState, type ResolvedOrbitCameraConfig, type Vec3, } from "./orbitCameraMath.js";
|
package/dist/camera/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { GameOrbitCamera } from "./GameOrbitCamera.js";
|
|
2
|
-
export { cameraFollowStep, DEFAULT_ORBIT_CAMERA, distanceBetween, GAME_SIM_FRAME_PRIORITY, lerpVec3, ORBIT_CAMERA_FRAME_PRIORITY, orbitFollowStep, orbitYawFromCamera, resolveFollowTargetFromPosition, resolveOrbitCameraConfig, resolveTargetSmoothing, seedOrbitFollowState, smoothBlend, } from "./orbitCameraMath.js";
|
|
2
|
+
export { cameraFollowStep, cameraLookPitch, DEFAULT_ORBIT_CAMERA, distanceBetween, GAME_SIM_FRAME_PRIORITY, lerpVec3, ORBIT_CAMERA_FRAME_PRIORITY, orbitFollowStep, orbitYawFromCamera, resolveFollowTargetFromPosition, resolveOrbitCameraConfig, resolveTargetSmoothing, seedOrbitFollowState, smoothBlend, } from "./orbitCameraMath.js";
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Horizontal facing derived from an orbit camera orbiting a target on the XZ plane. */
|
|
2
2
|
export declare function orbitYawFromCamera(cameraX: number, cameraZ: number, targetX: number, targetZ: number): number;
|
|
3
|
+
/** Elevation the camera looks along (radians): negative = aiming down, positive = aiming up, 0 = level. Feeds aim.pitch so vertical aim tracks the camera. */
|
|
4
|
+
export declare function cameraLookPitch(camera: Vec3, target: Vec3): number;
|
|
3
5
|
export interface Vec3 {
|
|
4
6
|
x: number;
|
|
5
7
|
y: number;
|
|
@@ -34,6 +36,9 @@ export interface OrbitCameraConfig {
|
|
|
34
36
|
dragTargetSmoothing?: number;
|
|
35
37
|
/** Exponential smoothing when re-locking orbit distance. */
|
|
36
38
|
distanceSmoothing?: number;
|
|
39
|
+
/** Vertical clamp (three.js polar angle, radians): 0 = top-down over the head, PI/2 = level, >PI/2 = look up from below the target. Widen to allow top-down or vertical aim; leave unset for the standard chase feel. */
|
|
40
|
+
minPolarAngle?: number;
|
|
41
|
+
maxPolarAngle?: number;
|
|
37
42
|
}
|
|
38
43
|
/** Fully resolved shell config after merging with DEFAULT_ORBIT_CAMERA. */
|
|
39
44
|
export interface ResolvedOrbitCameraConfig {
|
|
@@ -51,6 +56,8 @@ export interface ResolvedOrbitCameraConfig {
|
|
|
51
56
|
targetSmoothing: number;
|
|
52
57
|
dragTargetSmoothing: number;
|
|
53
58
|
distanceSmoothing: number;
|
|
59
|
+
minPolarAngle: number;
|
|
60
|
+
maxPolarAngle: number;
|
|
54
61
|
}
|
|
55
62
|
export declare const DEFAULT_ORBIT_CAMERA: ResolvedOrbitCameraConfig;
|
|
56
63
|
/** Run simulation/movement before orbit follow so poses are current. */
|
|
@@ -4,6 +4,11 @@ export function orbitYawFromCamera(cameraX, cameraZ, targetX, targetZ) {
|
|
|
4
4
|
const dz = targetZ - cameraZ;
|
|
5
5
|
return Math.atan2(dx, dz);
|
|
6
6
|
}
|
|
7
|
+
/** Elevation the camera looks along (radians): negative = aiming down, positive = aiming up, 0 = level. Feeds aim.pitch so vertical aim tracks the camera. */
|
|
8
|
+
export function cameraLookPitch(camera, target) {
|
|
9
|
+
const horizontal = Math.hypot(target.x - camera.x, target.z - camera.z);
|
|
10
|
+
return Math.atan2(target.y - camera.y, horizontal);
|
|
11
|
+
}
|
|
7
12
|
export const DEFAULT_ORBIT_CAMERA = {
|
|
8
13
|
minDistance: 4,
|
|
9
14
|
maxDistance: 28,
|
|
@@ -18,6 +23,8 @@ export const DEFAULT_ORBIT_CAMERA = {
|
|
|
18
23
|
targetSmoothing: 8,
|
|
19
24
|
dragTargetSmoothing: 11,
|
|
20
25
|
distanceSmoothing: 5,
|
|
26
|
+
minPolarAngle: 0.12,
|
|
27
|
+
maxPolarAngle: Math.PI / 2.05,
|
|
21
28
|
};
|
|
22
29
|
/** Run simulation/movement before orbit follow so poses are current. */
|
|
23
30
|
export const GAME_SIM_FRAME_PRIORITY = 0;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function WorldEntityBars({ statId, height }: {
|
|
2
|
+
statId: string;
|
|
3
|
+
height?: number;
|
|
4
|
+
}): import("react").JSX.Element;
|
|
5
|
+
export declare function WorldFloatText({ height, lifeMs }: {
|
|
6
|
+
height?: number;
|
|
7
|
+
lifeMs?: number;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
9
|
+
export declare function ProjectileTracers({ lifeMs }: {
|
|
10
|
+
lifeMs?: number;
|
|
11
|
+
}): import("react").JSX.Element;
|
|
12
|
+
export declare function Reticle({ className }: {
|
|
13
|
+
className?: string;
|
|
14
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,78 @@
|
|
|
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 { useEffect, useRef, useState } from "react";
|
|
4
|
+
import * as THREE from "three";
|
|
5
|
+
import { useGameContext } from "@jgengine/react/provider";
|
|
6
|
+
import { useEntityStat, useSceneEntities } from "@jgengine/react/hooks";
|
|
7
|
+
const MUZZLE_HEIGHT = 1.4;
|
|
8
|
+
function EntityBar({ entity, statId, height }) {
|
|
9
|
+
const stat = useEntityStat(entity.id, statId);
|
|
10
|
+
if (stat === null)
|
|
11
|
+
return null;
|
|
12
|
+
const range = stat.max - stat.min;
|
|
13
|
+
const percent = range <= 0 ? 0 : Math.max(0, Math.min(1, (stat.current - stat.min) / range));
|
|
14
|
+
return (_jsx(Html, { position: [entity.position[0], entity.position[1] + height, entity.position[2]], center: true, distanceFactor: 12, zIndexRange: [20, 0], children: _jsx("div", { className: "h-2.5 w-28 overflow-hidden rounded-sm border border-black/70 bg-black/70 shadow", children: _jsx("div", { className: "h-full bg-gradient-to-r from-rose-600 to-red-400", style: { width: `${percent * 100}%` } }) }) }));
|
|
15
|
+
}
|
|
16
|
+
export function WorldEntityBars({ statId, height = 2.2 }) {
|
|
17
|
+
const ctx = useGameContext();
|
|
18
|
+
const entities = useSceneEntities();
|
|
19
|
+
const playerId = ctx.player.userId;
|
|
20
|
+
return (_jsx(_Fragment, { children: entities.map((entity) => entity.id === playerId ? null : (_jsx(EntityBar, { entity: entity, statId: statId, height: height }, entity.id))) }));
|
|
21
|
+
}
|
|
22
|
+
function FloatTextItem({ text, kind, lifeMs }) {
|
|
23
|
+
const [shown, setShown] = useState(false);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const raf = requestAnimationFrame(() => setShown(true));
|
|
26
|
+
return () => cancelAnimationFrame(raf);
|
|
27
|
+
}, []);
|
|
28
|
+
return (_jsx("span", { className: kind === "heal" ? "text-emerald-300" : "text-amber-200", style: {
|
|
29
|
+
display: "inline-block",
|
|
30
|
+
fontWeight: 800,
|
|
31
|
+
fontSize: "18px",
|
|
32
|
+
textShadow: "0 1px 3px rgba(0,0,0,0.95)",
|
|
33
|
+
transition: `transform ${lifeMs}ms ease-out, opacity ${lifeMs}ms ease-out`,
|
|
34
|
+
transform: shown ? "translateY(-30px)" : "translateY(0)",
|
|
35
|
+
opacity: shown ? 0 : 1,
|
|
36
|
+
}, children: text }));
|
|
37
|
+
}
|
|
38
|
+
export function WorldFloatText({ height = 1.9, lifeMs = 950 }) {
|
|
39
|
+
const ctx = useGameContext();
|
|
40
|
+
const [floaters, setFloaters] = useState([]);
|
|
41
|
+
const nextId = useRef(0);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
return ctx.game.events.on("entity.floatText", (event) => {
|
|
44
|
+
const id = nextId.current++;
|
|
45
|
+
setFloaters((current) => [
|
|
46
|
+
...current,
|
|
47
|
+
{ id, position: event.position, text: event.text, kind: event.kind },
|
|
48
|
+
]);
|
|
49
|
+
window.setTimeout(() => setFloaters((current) => current.filter((floater) => floater.id !== id)), lifeMs);
|
|
50
|
+
});
|
|
51
|
+
}, [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
|
+
}
|
|
54
|
+
export function ProjectileTracers({ lifeMs = 130 }) {
|
|
55
|
+
const ctx = useGameContext();
|
|
56
|
+
const [tracers, setTracers] = useState([]);
|
|
57
|
+
const nextId = useRef(0);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
return ctx.game.events.on("projectile.settled", (event) => {
|
|
60
|
+
const id = nextId.current++;
|
|
61
|
+
setTracers((current) => [
|
|
62
|
+
...current,
|
|
63
|
+
{
|
|
64
|
+
id,
|
|
65
|
+
points: [
|
|
66
|
+
new THREE.Vector3(event.origin[0], event.origin[1] + MUZZLE_HEIGHT, event.origin[2]),
|
|
67
|
+
new THREE.Vector3(event.at[0], event.at[1], event.at[2]),
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
]);
|
|
71
|
+
window.setTimeout(() => setTracers((current) => current.filter((tracer) => tracer.id !== id)), lifeMs);
|
|
72
|
+
});
|
|
73
|
+
}, [ctx, lifeMs]);
|
|
74
|
+
return (_jsx(_Fragment, { children: tracers.map((tracer) => (_jsx(Line, { points: tracer.points, color: "#fde68a", lineWidth: 2, transparent: true, opacity: 0.85 }, tracer.id))) }));
|
|
75
|
+
}
|
|
76
|
+
export function Reticle({ className }) {
|
|
77
|
+
return (_jsx("div", { className: className ?? "pointer-events-none absolute inset-0 z-20 flex items-center justify-center", children: _jsxs("div", { className: "relative h-6 w-6", children: [_jsx("span", { className: "absolute left-1/2 top-1/2 h-1.5 w-1.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-white/90 shadow" }), _jsx("span", { className: "absolute left-1/2 top-0 h-2 w-px -translate-x-1/2 bg-white/80" }), _jsx("span", { className: "absolute bottom-0 left-1/2 h-2 w-px -translate-x-1/2 bg-white/80" }), _jsx("span", { className: "absolute left-0 top-1/2 h-px w-2 -translate-y-1/2 bg-white/80" }), _jsx("span", { className: "absolute right-0 top-1/2 h-px w-2 -translate-y-1/2 bg-white/80" })] }) }));
|
|
78
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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",
|
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
"url": "git+https://github.com/Noisemaker111/jgengine.git",
|
|
11
11
|
"directory": "packages/shell"
|
|
12
12
|
},
|
|
13
|
-
"files": [
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"CHANGELOG.md"
|
|
16
|
+
],
|
|
14
17
|
"exports": {
|
|
15
18
|
"./*": {
|
|
16
19
|
"types": "./dist/*.d.ts",
|
|
@@ -18,14 +21,14 @@
|
|
|
18
21
|
}
|
|
19
22
|
},
|
|
20
23
|
"scripts": {
|
|
21
|
-
"build": "
|
|
22
|
-
"check-types": "
|
|
24
|
+
"build": "tsgo -p tsconfig.build.json && bun ../../scripts/fix-extensions.ts dist",
|
|
25
|
+
"check-types": "tsgo --noEmit -p tsconfig.json",
|
|
23
26
|
"test": "bun test src"
|
|
24
27
|
},
|
|
25
28
|
"dependencies": {
|
|
26
|
-
"@jgengine/core": "^0.
|
|
27
|
-
"@jgengine/react": "^0.
|
|
28
|
-
"@jgengine/ws": "^0.
|
|
29
|
+
"@jgengine/core": "^0.5.0",
|
|
30
|
+
"@jgengine/react": "^0.5.0",
|
|
31
|
+
"@jgengine/ws": "^0.5.0"
|
|
29
32
|
},
|
|
30
33
|
"peerDependencies": {
|
|
31
34
|
"@react-three/drei": "^10.0.0",
|