@jgengine/shell 0.7.0 → 0.8.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 +47 -0
- package/README.md +26 -1
- package/dist/GameHost.d.ts +12 -0
- package/dist/GameHost.js +33 -0
- package/dist/GamePlayer.d.ts +11 -0
- package/dist/GamePlayer.js +24 -0
- package/dist/GamePlayerShell.d.ts +33 -1
- package/dist/GamePlayerShell.js +439 -28
- package/dist/behaviour.d.ts +31 -0
- package/dist/behaviour.js +63 -0
- package/dist/camera/GameCameraRig.d.ts +8 -3
- package/dist/camera/GameCameraRig.js +28 -14
- package/dist/camera/GameFirstPersonCamera.js +2 -0
- package/dist/camera/GameInspectionCamera.d.ts +6 -0
- package/dist/camera/GameInspectionCamera.js +24 -0
- package/dist/camera/cameraRigs.d.ts +5 -1
- package/dist/camera/cameraRigs.js +43 -6
- package/dist/camera/index.d.ts +4 -2
- package/dist/camera/index.js +4 -2
- package/dist/camera/inspectionCameraMath.d.ts +25 -0
- package/dist/camera/inspectionCameraMath.js +44 -0
- package/dist/camera/rigMath.d.ts +40 -1
- package/dist/camera/rigMath.js +55 -0
- package/dist/camera/rigResolve.d.ts +15 -0
- package/dist/camera/rigResolve.js +52 -0
- package/dist/defineGame.d.ts +16 -0
- package/dist/defineGame.js +51 -0
- package/dist/devtools/DevtoolsOverlay.d.ts +14 -0
- package/dist/devtools/DevtoolsOverlay.js +334 -0
- package/dist/environment/Daylight.d.ts +50 -0
- package/dist/environment/Daylight.js +100 -0
- package/dist/environment/EnvironmentScene.js +18 -26
- package/dist/environment/GroundPad.d.ts +7 -0
- package/dist/environment/GroundPad.js +12 -0
- package/dist/environment/daylightCycle.d.ts +26 -0
- package/dist/environment/daylightCycle.js +116 -0
- package/dist/environment/groundPadMath.d.ts +13 -0
- package/dist/environment/groundPadMath.js +10 -0
- package/dist/environment/index.d.ts +2 -0
- package/dist/environment/index.js +2 -0
- package/dist/materialOverride.d.ts +8 -0
- package/dist/materialOverride.js +32 -0
- package/dist/multiplayer.d.ts +16 -9
- package/dist/multiplayer.js +62 -12
- package/dist/pointer/PointerProbe.js +14 -1
- package/dist/pointer/pointerService.d.ts +2 -0
- package/dist/pointer/pointerService.js +45 -24
- package/dist/registry.d.ts +4 -1
- package/dist/registry.js +3 -1
- package/dist/render/modelRender.d.ts +14 -0
- package/dist/render/modelRender.js +79 -0
- package/dist/terrain/index.d.ts +1 -1
- package/dist/terrain/index.js +1 -1
- package/dist/terrain/terrainMath.d.ts +1 -0
- package/dist/terrain/terrainMath.js +8 -2
- package/dist/touch/TouchControlsOverlay.d.ts +18 -0
- package/dist/touch/TouchControlsOverlay.js +173 -0
- package/dist/water/OceanShader.d.ts +1 -1
- package/dist/water/OceanShader.js +3 -3
- package/dist/world/DataObjects.d.ts +44 -0
- package/dist/world/DataObjects.js +75 -0
- package/dist/world/GridWorldScene.d.ts +10 -0
- package/dist/world/GridWorldScene.js +54 -0
- package/dist/world/WorldHud.d.ts +5 -1
- package/dist/world/WorldHud.js +6 -2
- package/dist/world/WorldItems.js +21 -2
- package/llms.txt +2382 -0
- package/package.json +6 -5
- package/dist/demo/builderDemo.d.ts +0 -2
- package/dist/demo/builderDemo.js +0 -189
- package/dist/demo/demoGame.d.ts +0 -2
- package/dist/demo/demoGame.js +0 -208
- package/dist/demo/environmentShowcase.d.ts +0 -2
- package/dist/demo/environmentShowcase.js +0 -15
- package/dist/demo/mapDemo.d.ts +0 -2
- package/dist/demo/mapDemo.js +0 -206
- package/dist/demo/pointerDemo.d.ts +0 -2
- package/dist/demo/pointerDemo.js +0 -151
- package/dist/demo/sensorShowcase.d.ts +0 -2
- package/dist/demo/sensorShowcase.js +0 -131
- package/dist/demo/survivalDemo.d.ts +0 -2
- package/dist/demo/survivalDemo.js +0 -291
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"dist",
|
|
15
|
-
"CHANGELOG.md"
|
|
15
|
+
"CHANGELOG.md",
|
|
16
|
+
"llms.txt"
|
|
16
17
|
],
|
|
17
18
|
"exports": {
|
|
18
19
|
"./weather": {
|
|
@@ -34,9 +35,9 @@
|
|
|
34
35
|
"test": "bun test src"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
|
-
"@jgengine/core": "^0.
|
|
38
|
-
"@jgengine/react": "^0.
|
|
39
|
-
"@jgengine/ws": "^0.
|
|
38
|
+
"@jgengine/core": "^0.8.0",
|
|
39
|
+
"@jgengine/react": "^0.8.0",
|
|
40
|
+
"@jgengine/ws": "^0.8.0"
|
|
40
41
|
},
|
|
41
42
|
"peerDependencies": {
|
|
42
43
|
"@react-three/drei": "^10.0.0",
|
package/dist/demo/builderDemo.js
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useThree } from "@react-three/fiber";
|
|
3
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { defineGame } from "@jgengine/core/game/defineGame";
|
|
5
|
-
import { createAssetCatalog } from "@jgengine/core/scene/assetCatalog";
|
|
6
|
-
import { noiseField } from "@jgengine/core/world/terrain";
|
|
7
|
-
import { createEditableTerrain, createTerraformBrush } from "@jgengine/core/world/terraform";
|
|
8
|
-
import { createPlacementController, } from "@jgengine/core/world/placementController";
|
|
9
|
-
import { createPlacedStructureStore, } from "@jgengine/core/world/placedStructureStore";
|
|
10
|
-
import { createPointerService } from "../pointer/pointerService.js";
|
|
11
|
-
import { EditableGround } from "../terrain/EditableGround.js";
|
|
12
|
-
import { TerraformBrushCursor } from "../terrain/TerraformBrushCursor.js";
|
|
13
|
-
import { PlacementGhost } from "../structures/PlacementGhost.js";
|
|
14
|
-
const BOUNDS = { minX: -20, minZ: -20, maxX: 20, maxZ: 20 };
|
|
15
|
-
const FOOTPRINT = { w: 3, d: 3 };
|
|
16
|
-
const HUT_HEIGHT = 2.4;
|
|
17
|
-
const terrain = createEditableTerrain({
|
|
18
|
-
bounds: BOUNDS,
|
|
19
|
-
base: noiseField({ seed: "builder", amplitude: 0.6, frequency: 0.04 }),
|
|
20
|
-
cellSize: 1,
|
|
21
|
-
});
|
|
22
|
-
const brush = createTerraformBrush(terrain, { radius: 3.5, strength: 0.6 });
|
|
23
|
-
const structures = createPlacedStructureStore();
|
|
24
|
-
function seedScene() {
|
|
25
|
-
structures.clear();
|
|
26
|
-
terrain.reset();
|
|
27
|
-
terrain.apply({ mode: "raise", center: [8, -6], radius: 7, strength: 3 });
|
|
28
|
-
terrain.apply({ mode: "flatten", center: [-6, 4], radius: 6, strength: 1, target: 0 });
|
|
29
|
-
for (let i = 0; i < 6; i += 1)
|
|
30
|
-
terrain.apply({ mode: "paint", center: [-14 + i * 3, -12 + i * 2], radius: 1.6, surface: "path" });
|
|
31
|
-
structures.add({ catalogId: "hut", position: [-6, terrain.sampleHeight(-6, 4), 4], rotationY: 0 });
|
|
32
|
-
structures.add({ catalogId: "hut", position: [-2, terrain.sampleHeight(-2, 6), 6], rotationY: Math.PI / 6 });
|
|
33
|
-
structures.add({ catalogId: "hut", position: [2, terrain.sampleHeight(2, 3), 3], rotationY: -Math.PI / 5 });
|
|
34
|
-
}
|
|
35
|
-
seedScene();
|
|
36
|
-
function Hut({ structure }) {
|
|
37
|
-
return (_jsxs("group", { position: [structure.position[0], structure.position[1], structure.position[2]], "rotation-y": structure.rotationY, children: [_jsxs("mesh", { "position-y": HUT_HEIGHT / 2, castShadow: true, children: [_jsx("boxGeometry", { args: [FOOTPRINT.w, HUT_HEIGHT, FOOTPRINT.d] }), _jsx("meshStandardMaterial", { color: "#b08968", roughness: 0.85 })] }), _jsxs("mesh", { "position-y": HUT_HEIGHT + 0.55, "rotation-y": Math.PI / 4, children: [_jsx("coneGeometry", { args: [FOOTPRINT.w * 0.82, 1.3, 4] }), _jsx("meshStandardMaterial", { color: "#6d4c3d", roughness: 0.9 })] })] }));
|
|
38
|
-
}
|
|
39
|
-
function obstaclesFrom(list) {
|
|
40
|
-
return list.map((s) => ({
|
|
41
|
-
id: s.id,
|
|
42
|
-
aabb: {
|
|
43
|
-
minX: s.position[0] - FOOTPRINT.w / 2,
|
|
44
|
-
maxX: s.position[0] + FOOTPRINT.w / 2,
|
|
45
|
-
minZ: s.position[2] - FOOTPRINT.d / 2,
|
|
46
|
-
maxZ: s.position[2] + FOOTPRINT.d / 2,
|
|
47
|
-
},
|
|
48
|
-
}));
|
|
49
|
-
}
|
|
50
|
-
function BuilderScene() {
|
|
51
|
-
const three = useThree();
|
|
52
|
-
const pointer = useMemo(() => createPointerService(), []);
|
|
53
|
-
const controller = useRef(createPlacementController({ footprint: FOOTPRINT, snapMode: "grid", grid: 1, rules: { bounds: BOUNDS } }));
|
|
54
|
-
const [preview, setPreview] = useState(null);
|
|
55
|
-
const [brushCenter, setBrushCenter] = useState([8, -6]);
|
|
56
|
-
const [placed, setPlaced] = useState(structures.list());
|
|
57
|
-
const [version, setVersion] = useState(0);
|
|
58
|
-
const stateRef = useRef({ mode: "build", terraformMode: "raise", snapMode: "grid" });
|
|
59
|
-
useEffect(() => structures.subscribe(() => setPlaced(structures.list())), []);
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
const seed = controller.current.hover({ point: [-6, terrain.sampleHeight(-6, -4), -4], normal: [0, 1, 0] });
|
|
62
|
-
setPreview(seed);
|
|
63
|
-
}, []);
|
|
64
|
-
useEffect(() => {
|
|
65
|
-
const dom = three.gl.domElement;
|
|
66
|
-
const onMove = (event) => {
|
|
67
|
-
const rect = dom.getBoundingClientRect();
|
|
68
|
-
const ndcX = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
|
69
|
-
const ndcY = -(((event.clientY - rect.top) / rect.height) * 2 - 1);
|
|
70
|
-
pointer.setCursor(ndcX, ndcY, true);
|
|
71
|
-
pointer.bind({ camera: three.camera, scene: three.scene, width: rect.width, height: rect.height });
|
|
72
|
-
const hit = pointer.worldHit();
|
|
73
|
-
if (hit === null)
|
|
74
|
-
return;
|
|
75
|
-
const st = stateRef.current;
|
|
76
|
-
if (st.mode === "build") {
|
|
77
|
-
controller.current.setRules({ bounds: BOUNDS, obstacles: obstaclesFrom(structures.list()) });
|
|
78
|
-
setPreview(controller.current.hover({ point: hit.point, normal: hit.normal }));
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
setBrushCenter([hit.point[0], hit.point[2]]);
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
const onLeave = () => pointer.setCursor(0, 0, false);
|
|
85
|
-
const onDown = (event) => {
|
|
86
|
-
if (event.button !== 0)
|
|
87
|
-
return;
|
|
88
|
-
const st = stateRef.current;
|
|
89
|
-
if (st.mode === "build") {
|
|
90
|
-
const commit = controller.current.commit();
|
|
91
|
-
if (commit === null)
|
|
92
|
-
return;
|
|
93
|
-
structures.add({
|
|
94
|
-
catalogId: "hut",
|
|
95
|
-
position: [commit.center[0], terrain.sampleHeight(commit.center[0], commit.center[1]), commit.center[1]],
|
|
96
|
-
rotationY: commit.rotationY,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
const center = brushCenter;
|
|
101
|
-
if (center === null)
|
|
102
|
-
return;
|
|
103
|
-
if (st.terraformMode === "raise")
|
|
104
|
-
brush.raise(center);
|
|
105
|
-
else if (st.terraformMode === "lower")
|
|
106
|
-
brush.lower(center);
|
|
107
|
-
else if (st.terraformMode === "flatten")
|
|
108
|
-
brush.flatten(center);
|
|
109
|
-
else
|
|
110
|
-
brush.paint(center, "gravel");
|
|
111
|
-
setVersion((v) => v + 1);
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
const onKey = (event) => {
|
|
115
|
-
const st = stateRef.current;
|
|
116
|
-
if (event.code === "KeyB")
|
|
117
|
-
st.mode = "build";
|
|
118
|
-
else if (event.code === "KeyT")
|
|
119
|
-
st.mode = "terraform";
|
|
120
|
-
else if (event.code === "KeyR")
|
|
121
|
-
setPreview(controller.current.rotate());
|
|
122
|
-
else if (event.code === "KeyG") {
|
|
123
|
-
st.snapMode = controller.current.cycleSnapMode();
|
|
124
|
-
setPreview(controller.current.current());
|
|
125
|
-
}
|
|
126
|
-
else if (event.code === "Digit1")
|
|
127
|
-
st.terraformMode = "raise";
|
|
128
|
-
else if (event.code === "Digit2")
|
|
129
|
-
st.terraformMode = "lower";
|
|
130
|
-
else if (event.code === "Digit3")
|
|
131
|
-
st.terraformMode = "flatten";
|
|
132
|
-
else if (event.code === "Digit4")
|
|
133
|
-
st.terraformMode = "paint";
|
|
134
|
-
};
|
|
135
|
-
dom.addEventListener("pointermove", onMove);
|
|
136
|
-
dom.addEventListener("pointerleave", onLeave);
|
|
137
|
-
dom.addEventListener("pointerdown", onDown);
|
|
138
|
-
window.addEventListener("keydown", onKey);
|
|
139
|
-
return () => {
|
|
140
|
-
dom.removeEventListener("pointermove", onMove);
|
|
141
|
-
dom.removeEventListener("pointerleave", onLeave);
|
|
142
|
-
dom.removeEventListener("pointerdown", onDown);
|
|
143
|
-
window.removeEventListener("keydown", onKey);
|
|
144
|
-
};
|
|
145
|
-
}, [pointer, three, brushCenter]);
|
|
146
|
-
const invalidGhost = useMemo(() => {
|
|
147
|
-
const c = createPlacementController({ footprint: FOOTPRINT, snapMode: "free", rules: { bounds: BOUNDS, obstacles: obstaclesFrom(structures.list()) } });
|
|
148
|
-
return c.hover({ point: [-6, terrain.sampleHeight(-6, 4) + 0.05, 4], normal: [0, 1, 0] });
|
|
149
|
-
}, [placed]);
|
|
150
|
-
return (_jsxs(_Fragment, { children: [_jsx("hemisphereLight", { args: ["#cfe8ff", "#3a3320", 0.8] }), _jsx(EditableGround, { terrain: terrain, bounds: BOUNDS, version: version, baseColor: "#4b7f3f" }), placed.map((structure) => (_jsx(Hut, { structure: structure }, structure.id))), _jsx(PlacementGhost, { preview: preview, height: HUT_HEIGHT }), _jsx(PlacementGhost, { preview: invalidGhost, height: HUT_HEIGHT }), _jsx(TerraformBrushCursor, { center: brushCenter, radius: brush.config().radius, mode: "raise" })] }));
|
|
151
|
-
}
|
|
152
|
-
const game = defineGame({
|
|
153
|
-
name: "builder-sandbox",
|
|
154
|
-
assets: createAssetCatalog(),
|
|
155
|
-
multiplayer: null,
|
|
156
|
-
inventories: {},
|
|
157
|
-
input: {
|
|
158
|
-
moveForward: ["KeyW"],
|
|
159
|
-
moveBack: ["KeyS"],
|
|
160
|
-
moveLeft: ["KeyA"],
|
|
161
|
-
moveRight: ["KeyD"],
|
|
162
|
-
},
|
|
163
|
-
});
|
|
164
|
-
function onInit(_ctx) {
|
|
165
|
-
seedScene();
|
|
166
|
-
}
|
|
167
|
-
function onNewPlayer(_ctx) { }
|
|
168
|
-
function onTick(_ctx, _dt) { }
|
|
169
|
-
function BuilderUI() {
|
|
170
|
-
return (_jsxs("div", { className: "pointer-events-none absolute inset-0 font-sans text-white", children: [_jsxs("div", { className: "absolute left-4 top-4 rounded-lg border border-amber-300/25 bg-neutral-900/80 px-4 py-3 shadow-xl backdrop-blur-sm", children: [_jsx("h1", { className: "text-sm font-semibold tracking-wide text-amber-200", children: "Build & Terraform Sandbox" }), _jsx("p", { className: "text-xs text-white/60", children: "Ghost placement \u00B7 valid/invalid tint \u00B7 terraform brush" })] }), _jsxs("div", { className: "absolute bottom-4 left-4 w-72 rounded-lg border border-white/15 bg-neutral-900/80 p-3 text-[13px] leading-6 shadow-xl backdrop-blur-sm", children: [_jsx("div", { className: "mb-1 border-b border-white/10 pb-1 text-[10px] font-semibold uppercase tracking-widest text-white/45", children: "Tools" }), _jsxs("ul", { className: "text-white/80", children: [_jsxs("li", { children: [_jsx("span", { className: "text-amber-300", children: "B" }), " \u2014 build mode \u00B7 ", _jsx("span", { className: "text-amber-300", children: "Left-click" }), " place hut"] }), _jsxs("li", { children: [_jsx("span", { className: "text-amber-300", children: "R" }), " \u2014 rotate ghost \u00B7 ", _jsx("span", { className: "text-amber-300", children: "G" }), " \u2014 grid/free/surface snap"] }), _jsxs("li", { children: [_jsx("span", { className: "text-amber-300", children: "T" }), " \u2014 terraform \u00B7 ", _jsx("span", { className: "text-amber-300", children: "1-4" }), " raise/lower/flatten/paint"] }), _jsxs("li", { children: [_jsx("span", { className: "text-emerald-300", children: "Green ghost" }), " valid \u00B7 ", _jsx("span", { className: "text-red-300", children: "Red ghost" }), " blocked"] })] })] })] }));
|
|
171
|
-
}
|
|
172
|
-
export const builderDemoGame = {
|
|
173
|
-
game,
|
|
174
|
-
content: {},
|
|
175
|
-
loop: { onInit, onNewPlayer, onTick },
|
|
176
|
-
GameUI: BuilderUI,
|
|
177
|
-
environment: BuilderScene,
|
|
178
|
-
camera: {
|
|
179
|
-
perspective: "third",
|
|
180
|
-
followEnabled: false,
|
|
181
|
-
initialDistance: 46,
|
|
182
|
-
initialHeight: 40,
|
|
183
|
-
minDistance: 14,
|
|
184
|
-
maxDistance: 120,
|
|
185
|
-
targetHeight: 1,
|
|
186
|
-
targetOffset: { x: 0, z: -2 },
|
|
187
|
-
maxPolarAngle: 1.15,
|
|
188
|
-
},
|
|
189
|
-
};
|
package/dist/demo/demoGame.d.ts
DELETED
package/dist/demo/demoGame.js
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { defineGame } from "@jgengine/core/game/defineGame";
|
|
3
|
-
import { createAssetCatalog } from "@jgengine/core/scene/assetCatalog";
|
|
4
|
-
import { CurrencyPill, HealthBar, SlotGrid } from "@jgengine/react/components";
|
|
5
|
-
import { useFeed, useGameClock, useGameStore, usePlayer, useTarget } from "@jgengine/react/hooks";
|
|
6
|
-
const HERO = "hero";
|
|
7
|
-
const MOB = "gloomSlime";
|
|
8
|
-
const LOOT_TABLE = "slime-drops";
|
|
9
|
-
const GOLD = "gold";
|
|
10
|
-
const HOTBAR = "hotbar";
|
|
11
|
-
const MELEE_DAMAGE = 6;
|
|
12
|
-
const MELEE_RANGE = 1.9;
|
|
13
|
-
const MELEE_COOLDOWN = 1.2;
|
|
14
|
-
const AGGRO_RADIUS = 11;
|
|
15
|
-
const MOB_SPAWNS = [
|
|
16
|
-
[7, 0, -6],
|
|
17
|
-
[-8, 0, 5],
|
|
18
|
-
[6, 0, 8],
|
|
19
|
-
];
|
|
20
|
-
const entityCatalog = {
|
|
21
|
-
[HERO]: {
|
|
22
|
-
stats: { health: { max: 100 }, mana: { max: 60 } },
|
|
23
|
-
receive: { damage: { order: ["health"] } },
|
|
24
|
-
movement: { poses: ["standing", "running", "crouch"], walkSpeed: 2.4 },
|
|
25
|
-
},
|
|
26
|
-
[MOB]: {
|
|
27
|
-
stats: { health: { max: 60 } },
|
|
28
|
-
receive: { damage: { order: ["health"] } },
|
|
29
|
-
onDeath: { drops: LOOT_TABLE },
|
|
30
|
-
role: "enemy",
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
const itemCatalog = {
|
|
34
|
-
sword: { use: "swingSword", weapon: { damage: 22 } },
|
|
35
|
-
zap: { use: "castZap", weapon: { damage: 35, manaCost: 12, range: 18 } },
|
|
36
|
-
};
|
|
37
|
-
const game = defineGame({
|
|
38
|
-
name: "gameplayer-demo",
|
|
39
|
-
assets: createAssetCatalog(),
|
|
40
|
-
multiplayer: null,
|
|
41
|
-
inventories: { [HOTBAR]: { slots: 4, hud: "hotbar" } },
|
|
42
|
-
time: { start: 8 * 3600, speeds: [1, 2, 3, 4] },
|
|
43
|
-
input: {
|
|
44
|
-
moveForward: ["KeyW"],
|
|
45
|
-
moveBack: ["KeyS"],
|
|
46
|
-
moveLeft: ["KeyA"],
|
|
47
|
-
moveRight: ["KeyD"],
|
|
48
|
-
jump: ["Space"],
|
|
49
|
-
sprint: ["Shift"],
|
|
50
|
-
turnLeft: ["KeyQ"],
|
|
51
|
-
turnRight: ["KeyE"],
|
|
52
|
-
tabTarget: ["Tab"],
|
|
53
|
-
clearTarget: ["Escape"],
|
|
54
|
-
slot1: ["Digit1"],
|
|
55
|
-
slot2: ["Digit2"],
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
|
-
let elapsed = 0;
|
|
59
|
-
let nextSpawnIndex = 0;
|
|
60
|
-
const meleeReadyAt = new Map();
|
|
61
|
-
function spawnMob(ctx) {
|
|
62
|
-
const at = MOB_SPAWNS[nextSpawnIndex % MOB_SPAWNS.length];
|
|
63
|
-
nextSpawnIndex += 1;
|
|
64
|
-
ctx.scene.entity.spawn(MOB, { position: at, role: "npc" });
|
|
65
|
-
}
|
|
66
|
-
function spawnHero(ctx) {
|
|
67
|
-
ctx.scene.entity.spawn(HERO, {
|
|
68
|
-
id: ctx.player.userId,
|
|
69
|
-
position: [0, 0, 0],
|
|
70
|
-
role: "player",
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
function onInit(ctx) {
|
|
74
|
-
elapsed = 0;
|
|
75
|
-
nextSpawnIndex = 0;
|
|
76
|
-
meleeReadyAt.clear();
|
|
77
|
-
ctx.game.loot.register({ id: LOOT_TABLE, entries: [{ currency: GOLD, count: [2, 5], weight: 1 }] });
|
|
78
|
-
ctx.game.feed.bind("entity.died");
|
|
79
|
-
ctx.game.events.on("entity.died", (event) => {
|
|
80
|
-
if (event.catalogId === MOB) {
|
|
81
|
-
meleeReadyAt.delete(event.instanceId);
|
|
82
|
-
spawnMob(ctx);
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
ctx.item.use.register({
|
|
86
|
-
swingSword: {
|
|
87
|
-
apply(state, input) {
|
|
88
|
-
const hits = state.scene.entity
|
|
89
|
-
.queryArc({ from: input.from, aim: input.aim ?? { yaw: 0, pitch: 0 }, radius: 3.2, halfAngleDeg: 70 })
|
|
90
|
-
.filter((id) => state.scene.entity.get(id)?.name === MOB);
|
|
91
|
-
for (const id of hits) {
|
|
92
|
-
state.scene.entity.effect({ from: input.from, to: id, effect: "damage", via: { item: input.itemId } });
|
|
93
|
-
}
|
|
94
|
-
return { state };
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
castZap: {
|
|
98
|
-
can(state, input) {
|
|
99
|
-
const targetId = state.scene.entity.getTarget(input.from);
|
|
100
|
-
if (targetId === null)
|
|
101
|
-
return { reason: "no-target" };
|
|
102
|
-
const range = state.item.weapon.getStat(input.itemId, "range") ?? Number.POSITIVE_INFINITY;
|
|
103
|
-
const distance = state.scene.entity.distance(input.from, targetId);
|
|
104
|
-
if (distance === null || distance > range)
|
|
105
|
-
return { reason: "out-of-range" };
|
|
106
|
-
const cost = state.item.weapon.getStat(input.itemId, "manaCost") ?? 0;
|
|
107
|
-
const mana = state.scene.entity.stats.get(input.from, "mana");
|
|
108
|
-
if (mana === null || mana.current < cost)
|
|
109
|
-
return { reason: "no-mana" };
|
|
110
|
-
return null;
|
|
111
|
-
},
|
|
112
|
-
apply(state, input) {
|
|
113
|
-
const targetId = state.scene.entity.getTarget(input.from);
|
|
114
|
-
if (targetId === null)
|
|
115
|
-
return { state, error: "no-target" };
|
|
116
|
-
const cost = state.item.weapon.getStat(input.itemId, "manaCost") ?? 0;
|
|
117
|
-
state.scene.entity.stats.delta(input.from, "mana", -cost);
|
|
118
|
-
state.scene.entity.effect({ from: input.from, to: targetId, effect: "damage", via: { item: input.itemId } });
|
|
119
|
-
return { state };
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
function onNewPlayer(ctx) {
|
|
125
|
-
spawnHero(ctx);
|
|
126
|
-
ctx.player.inventory.put(HOTBAR, "sword", 1);
|
|
127
|
-
ctx.player.inventory.put(HOTBAR, "zap", 1);
|
|
128
|
-
ctx.game.economy.grant(ctx.player.userId, GOLD, 10);
|
|
129
|
-
for (let index = 0; index < MOB_SPAWNS.length; index += 1)
|
|
130
|
-
spawnMob(ctx);
|
|
131
|
-
}
|
|
132
|
-
function onTick(ctx, dt) {
|
|
133
|
-
elapsed += dt;
|
|
134
|
-
const playerId = ctx.player.userId;
|
|
135
|
-
const player = ctx.scene.entity.get(playerId);
|
|
136
|
-
if (player === null) {
|
|
137
|
-
spawnHero(ctx);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
const mana = ctx.scene.entity.stats.get(playerId, "mana");
|
|
141
|
-
if (mana !== null && mana.current < mana.max)
|
|
142
|
-
ctx.scene.entity.stats.delta(playerId, "mana", 5 * dt);
|
|
143
|
-
for (const mob of ctx.scene.entity.list()) {
|
|
144
|
-
if (mob.name !== MOB)
|
|
145
|
-
continue;
|
|
146
|
-
const distance = ctx.scene.entity.distance(mob.id, playerId);
|
|
147
|
-
if (distance === null || distance > AGGRO_RADIUS)
|
|
148
|
-
continue;
|
|
149
|
-
if (distance > MELEE_RANGE) {
|
|
150
|
-
const next = ctx.scene.entity.moveToward(mob.id, playerId, {
|
|
151
|
-
speed: 2,
|
|
152
|
-
stopDistance: MELEE_RANGE * 0.8,
|
|
153
|
-
dt,
|
|
154
|
-
});
|
|
155
|
-
if (next !== null) {
|
|
156
|
-
ctx.scene.entity.setPose(mob.id, {
|
|
157
|
-
position: next,
|
|
158
|
-
rotationY: Math.atan2(player.position[0] - next[0], player.position[2] - next[2]),
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
else if ((meleeReadyAt.get(mob.id) ?? 0) <= elapsed) {
|
|
163
|
-
meleeReadyAt.set(mob.id, elapsed + MELEE_COOLDOWN);
|
|
164
|
-
ctx.scene.entity.effect({ from: mob.id, to: playerId, effect: "damage", via: { amount: MELEE_DAMAGE } });
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
function VitalsPanel({ userId }) {
|
|
169
|
-
return (_jsxs("div", { className: "rounded bg-black/60 p-2", children: [_jsx(HealthBar, { instanceId: userId, statId: "health", className: "h-2.5 overflow-hidden rounded bg-white/15", fillClassName: "bg-emerald-400" }), _jsx(HealthBar, { instanceId: userId, statId: "mana", className: "mt-1 h-1.5 overflow-hidden rounded bg-white/15", fillClassName: "bg-sky-400" })] }));
|
|
170
|
-
}
|
|
171
|
-
function TargetPanel({ userId }) {
|
|
172
|
-
const targetId = useTarget(userId);
|
|
173
|
-
const targetName = useGameStore((ctx) => targetId === null ? null : (ctx.scene.entity.get(targetId)?.name ?? null));
|
|
174
|
-
if (targetId === null || targetName === null) {
|
|
175
|
-
return _jsx("p", { className: "rounded bg-black/50 px-2 py-1 text-center text-xs text-white/50", children: "Tab: target a mob" });
|
|
176
|
-
}
|
|
177
|
-
return (_jsxs("div", { className: "rounded bg-black/60 p-2", children: [_jsx("p", { className: "text-xs uppercase tracking-wide text-red-300", children: targetName }), _jsx(HealthBar, { instanceId: targetId, statId: "health", className: "mt-1 h-2 overflow-hidden rounded bg-white/15", fillClassName: "bg-red-400" })] }));
|
|
178
|
-
}
|
|
179
|
-
function KillFeedPanel() {
|
|
180
|
-
const kills = useFeed({ action: "entity.died", limit: 5 });
|
|
181
|
-
return (_jsx("ul", { className: "space-y-0.5 text-right text-xs text-white/70", children: kills
|
|
182
|
-
.slice()
|
|
183
|
-
.reverse()
|
|
184
|
-
.map((entry, index) => (_jsxs("li", { children: [entry.data.catalogId ?? "?", " down"] }, `${entry.at}-${index}`))) }));
|
|
185
|
-
}
|
|
186
|
-
function HotbarPanel() {
|
|
187
|
-
return (_jsx(SlotGrid, { inventoryId: HOTBAR, className: "flex gap-1.5", renderSlot: (slot, index) => (_jsxs("div", { className: "w-16 rounded border border-white/20 bg-black/60 px-1.5 py-1 text-center text-[11px]", children: [_jsx("span", { className: "text-white/50", children: index + 1 }), _jsx("p", { className: "truncate text-white", children: slot === null ? "—" : slot.itemId })] })) }));
|
|
188
|
-
}
|
|
189
|
-
function ClockPanel() {
|
|
190
|
-
const { paused, playSpeed, speeds, calendar, controls } = useGameClock();
|
|
191
|
-
const pad = (value) => value.toString().padStart(2, "0");
|
|
192
|
-
return (_jsxs("div", { className: "pointer-events-auto flex items-center gap-2 rounded bg-black/60 px-2 py-1 text-xs", children: [_jsxs("span", { className: "tabular-nums text-white", children: ["Day ", calendar.day + 1, " \u00B7 ", pad(calendar.hour), ":", pad(calendar.minute)] }), _jsx("button", { onClick: () => controls.toggle(), className: "rounded border border-white/25 px-1.5 py-0.5 text-white/80 hover:bg-white/10", children: paused ? "▶" : "⏸" }), _jsx("div", { className: "flex gap-0.5", children: speeds.map((speed) => (_jsxs("button", { onClick: () => controls.setSpeed(speed), className: `rounded border px-1.5 py-0.5 ${!paused && speed === playSpeed
|
|
193
|
-
? "border-amber-300 bg-amber-300/20 text-amber-200"
|
|
194
|
-
: "border-white/25 text-white/70 hover:bg-white/10"}`, children: [speed, "\u00D7"] }, speed))) })] }));
|
|
195
|
-
}
|
|
196
|
-
function DemoGameUI() {
|
|
197
|
-
const player = usePlayer();
|
|
198
|
-
return (_jsxs("div", { className: "pointer-events-none absolute inset-0 font-mono text-white", children: [_jsx("div", { className: "absolute left-4 top-4", children: _jsx(ClockPanel, {}) }), _jsx("div", { className: "absolute left-1/2 top-4 w-56 -translate-x-1/2", children: _jsx(TargetPanel, { userId: player.userId }) }), _jsxs("div", { className: "absolute right-4 top-4 flex flex-col items-end gap-2", children: [_jsx(CurrencyPill, { currencyId: GOLD, className: "rounded bg-black/60 px-2 py-1 text-sm text-amber-300" }), _jsx(KillFeedPanel, {})] }), _jsx("div", { className: "absolute bottom-4 left-4 w-64", children: _jsx(VitalsPanel, { userId: player.userId }) }), _jsx("div", { className: "absolute bottom-4 left-1/2 -translate-x-1/2", children: _jsx(HotbarPanel, {}) }), _jsx("div", { className: "absolute bottom-4 right-4 max-w-[220px] text-right text-[11px] leading-4 text-white/40", children: "WASD move \u00B7 Space jump \u00B7 Shift sprint \u00B7 Q/E turn \u00B7 Tab target \u00B7 Esc clear \u00B7 1 sword \u00B7 2 zap" })] }));
|
|
199
|
-
}
|
|
200
|
-
export const demoGame = {
|
|
201
|
-
game,
|
|
202
|
-
content: {
|
|
203
|
-
itemById: (itemId) => itemCatalog[itemId] ?? null,
|
|
204
|
-
entityById: (catalogId) => entityCatalog[catalogId] ?? null,
|
|
205
|
-
},
|
|
206
|
-
loop: { onInit, onNewPlayer, onTick },
|
|
207
|
-
GameUI: DemoGameUI,
|
|
208
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { building, environment, grass, ocean, rain, terrain, } from "@jgengine/core/world/features";
|
|
3
|
-
import { EnvironmentScene } from "../environment/EnvironmentScene.js";
|
|
4
|
-
import { demoGame } from "./demoGame.js";
|
|
5
|
-
const showcase = environment({
|
|
6
|
-
terrain: terrain({ bounds: { w: 220, d: 220 }, height: 4, frequency: 0.035, seed: "showcase", waterLevel: -1 }),
|
|
7
|
-
vegetation: grass({ area: { w: 120, d: 120 }, density: 5, colors: ["#31531f", "#8fbf4a"], seed: "showcase" }),
|
|
8
|
-
water: ocean({ bounds: { w: 520, d: 520 }, level: -1.4, waveHeight: 0.9, waveSpeed: 0.5, color: "#1f6f92" }),
|
|
9
|
-
weather: rain({ area: { w: 160, d: 160, h: 70 }, density: 0.5, wind: [1.4, 0.4] }),
|
|
10
|
-
structures: building({ count: 6, footprint: { w: 12, d: 9 }, stories: [3, 7], storyHeight: 2.8, spacing: 6, seed: "showcase" }),
|
|
11
|
-
});
|
|
12
|
-
export const environmentShowcaseGame = {
|
|
13
|
-
...demoGame,
|
|
14
|
-
environment: () => _jsx(EnvironmentScene, { feature: showcase }),
|
|
15
|
-
};
|
package/dist/demo/mapDemo.d.ts
DELETED