@jgengine/shell 0.7.0 → 0.9.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 (168) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +37 -1
  3. package/dist/GameHost.d.ts +12 -0
  4. package/dist/GameHost.js +33 -0
  5. package/dist/GamePlayer.d.ts +11 -0
  6. package/dist/GamePlayer.js +24 -0
  7. package/dist/GamePlayerShell.d.ts +36 -1
  8. package/dist/GamePlayerShell.js +751 -80
  9. package/dist/GameUiPreview.js +2 -1
  10. package/dist/audio/AudioComponents.js +25 -8
  11. package/dist/audio/audioEngine.d.ts +1 -0
  12. package/dist/audio/audioEngine.js +4 -0
  13. package/dist/behaviour.d.ts +12 -0
  14. package/dist/behaviour.js +27 -0
  15. package/dist/behaviourAttach.d.ts +9 -0
  16. package/dist/behaviourAttach.js +32 -0
  17. package/dist/behaviourDriver.d.ts +7 -0
  18. package/dist/behaviourDriver.js +21 -0
  19. package/dist/camera/GameCameraRig.d.ts +8 -3
  20. package/dist/camera/GameCameraRig.js +28 -14
  21. package/dist/camera/GameFirstPersonCamera.js +16 -0
  22. package/dist/camera/GameInspectionCamera.d.ts +6 -0
  23. package/dist/camera/GameInspectionCamera.js +24 -0
  24. package/dist/camera/GameOrbitCamera.js +10 -1
  25. package/dist/camera/PlayerFov.d.ts +18 -0
  26. package/dist/camera/PlayerFov.js +48 -0
  27. package/dist/camera/cameraBlendMath.d.ts +13 -0
  28. package/dist/camera/cameraBlendMath.js +36 -0
  29. package/dist/camera/cameraRigs.d.ts +8 -1
  30. package/dist/camera/cameraRigs.js +78 -38
  31. package/dist/camera/fovPreference.d.ts +25 -0
  32. package/dist/camera/fovPreference.js +73 -0
  33. package/dist/camera/index.d.ts +7 -2
  34. package/dist/camera/index.js +7 -2
  35. package/dist/camera/inspectionCameraMath.d.ts +25 -0
  36. package/dist/camera/inspectionCameraMath.js +44 -0
  37. package/dist/camera/rigMath.d.ts +50 -1
  38. package/dist/camera/rigMath.js +91 -1
  39. package/dist/camera/rigResolve.d.ts +15 -0
  40. package/dist/camera/rigResolve.js +52 -0
  41. package/dist/camera/shakeChannel.d.ts +3 -17
  42. package/dist/camera/shakeChannel.js +2 -21
  43. package/dist/camera/shakeChannelMath.d.ts +8 -0
  44. package/dist/camera/shakeChannelMath.js +21 -0
  45. package/dist/cartridge.d.ts +145 -0
  46. package/dist/cartridge.js +245 -0
  47. package/dist/defineGame.d.ts +16 -0
  48. package/dist/defineGame.js +52 -0
  49. package/dist/devtools/CollisionDebugWorld.d.ts +5 -0
  50. package/dist/devtools/CollisionDebugWorld.js +180 -0
  51. package/dist/devtools/DevtoolsOverlay.d.ts +78 -0
  52. package/dist/devtools/DevtoolsOverlay.js +676 -0
  53. package/dist/devtools/collisionDebug.d.ts +57 -0
  54. package/dist/devtools/collisionDebug.js +127 -0
  55. package/dist/devtools/collisionDebugMath.d.ts +102 -0
  56. package/dist/devtools/collisionDebugMath.js +128 -0
  57. package/dist/environment/Daylight.d.ts +56 -0
  58. package/dist/environment/Daylight.js +101 -0
  59. package/dist/environment/EnvironmentScene.js +87 -48
  60. package/dist/environment/GroundPad.d.ts +7 -0
  61. package/dist/environment/GroundPad.js +12 -0
  62. package/dist/environment/daylightCycle.d.ts +26 -0
  63. package/dist/environment/daylightCycle.js +116 -0
  64. package/dist/environment/groundPadMath.d.ts +13 -0
  65. package/dist/environment/groundPadMath.js +10 -0
  66. package/dist/environment/index.d.ts +3 -0
  67. package/dist/environment/index.js +3 -0
  68. package/dist/environment/skyLightingPolicy.d.ts +10 -0
  69. package/dist/environment/skyLightingPolicy.js +6 -0
  70. package/dist/input/mouseLook.d.ts +27 -0
  71. package/dist/input/mouseLook.js +35 -0
  72. package/dist/materialOverride.d.ts +6 -0
  73. package/dist/materialOverride.js +28 -0
  74. package/dist/multiplayer.d.ts +16 -9
  75. package/dist/multiplayer.js +62 -12
  76. package/dist/pointer/PointerProbe.js +20 -3
  77. package/dist/pointer/pointerService.d.ts +5 -0
  78. package/dist/pointer/pointerService.js +51 -24
  79. package/dist/registry.d.ts +4 -1
  80. package/dist/registry.js +3 -1
  81. package/dist/render/modelRender.d.ts +23 -0
  82. package/dist/render/modelRender.js +106 -0
  83. package/dist/render/resolveModel.d.ts +14 -0
  84. package/dist/render/resolveModel.js +24 -0
  85. package/dist/settings/QuickControls.d.ts +4 -0
  86. package/dist/settings/QuickControls.js +42 -0
  87. package/dist/settings/SettingsChrome.d.ts +1 -0
  88. package/dist/settings/SettingsChrome.js +10 -0
  89. package/dist/settings/SettingsMenu.d.ts +6 -0
  90. package/dist/settings/SettingsMenu.js +148 -0
  91. package/dist/settings/SettingsRuntime.d.ts +11 -0
  92. package/dist/settings/SettingsRuntime.js +19 -0
  93. package/dist/settings/appliedSettings.d.ts +14 -0
  94. package/dist/settings/appliedSettings.js +27 -0
  95. package/dist/settings/settingsController.d.ts +20 -0
  96. package/dist/settings/settingsController.js +165 -0
  97. package/dist/structures/GeneratedBuilding.d.ts +10 -0
  98. package/dist/structures/GeneratedBuilding.js +96 -8
  99. package/dist/structures/index.d.ts +1 -1
  100. package/dist/structures/index.js +1 -1
  101. package/dist/terrain/CarvedTerrain.d.ts +2 -1
  102. package/dist/terrain/CarvedTerrain.js +3 -3
  103. package/dist/terrain/GrassField.d.ts +3 -1
  104. package/dist/terrain/GrassField.js +4 -2
  105. package/dist/terrain/grassBudget.d.ts +3 -0
  106. package/dist/terrain/grassBudget.js +6 -0
  107. package/dist/terrain/grassGeometry.js +1 -1
  108. package/dist/terrain/index.d.ts +1 -1
  109. package/dist/terrain/index.js +1 -1
  110. package/dist/terrain/terrainMath.d.ts +9 -0
  111. package/dist/terrain/terrainMath.js +17 -2
  112. package/dist/touch/OrientationHint.d.ts +3 -0
  113. package/dist/touch/OrientationHint.js +13 -0
  114. package/dist/touch/TouchControlsOverlay.d.ts +34 -0
  115. package/dist/touch/TouchControlsOverlay.js +279 -0
  116. package/dist/visibility/CullingProvider.d.ts +21 -0
  117. package/dist/visibility/CullingProvider.js +134 -0
  118. package/dist/vision/FrustumSensorHud.d.ts +1 -6
  119. package/dist/vision/FrustumSensorHud.js +25 -27
  120. package/dist/vision/frustumSampleEqual.d.ts +2 -0
  121. package/dist/vision/frustumSampleEqual.js +10 -0
  122. package/dist/water/Ocean.js +1 -1
  123. package/dist/water/OceanConfig.d.ts +13 -0
  124. package/dist/water/OceanConfig.js +25 -17
  125. package/dist/water/OceanShader.d.ts +1 -1
  126. package/dist/water/OceanShader.js +3 -3
  127. package/dist/water/index.d.ts +1 -1
  128. package/dist/water/index.js +1 -1
  129. package/dist/weather/FireSpreadLayer.js +7 -2
  130. package/dist/weather/RainField.d.ts +3 -1
  131. package/dist/weather/RainField.js +4 -4
  132. package/dist/weather/SnowField.d.ts +3 -1
  133. package/dist/weather/SnowField.js +4 -4
  134. package/dist/weather/fireSpreadPose.d.ts +2 -0
  135. package/dist/weather/fireSpreadPose.js +4 -0
  136. package/dist/weather/weatherMath.d.ts +5 -1
  137. package/dist/weather/weatherMath.js +7 -2
  138. package/dist/world/DataObjects.d.ts +44 -0
  139. package/dist/world/DataObjects.js +77 -0
  140. package/dist/world/GridWorldScene.d.ts +10 -0
  141. package/dist/world/GridWorldScene.js +54 -0
  142. package/dist/world/SpriteBatch.d.ts +44 -0
  143. package/dist/world/SpriteBatch.js +112 -0
  144. package/dist/world/WorldHud.d.ts +8 -1
  145. package/dist/world/WorldHud.js +90 -39
  146. package/dist/world/WorldItems.js +21 -2
  147. package/dist/world/entityPose.d.ts +14 -0
  148. package/dist/world/entityPose.js +10 -0
  149. package/dist/world/telegraphPulse.d.ts +1 -0
  150. package/dist/world/telegraphPulse.js +4 -0
  151. package/dist/world/worldBarSamples.d.ts +30 -0
  152. package/dist/world/worldBarSamples.js +51 -0
  153. package/llms.txt +2643 -0
  154. package/package.json +6 -5
  155. package/dist/demo/builderDemo.d.ts +0 -2
  156. package/dist/demo/builderDemo.js +0 -189
  157. package/dist/demo/demoGame.d.ts +0 -2
  158. package/dist/demo/demoGame.js +0 -208
  159. package/dist/demo/environmentShowcase.d.ts +0 -2
  160. package/dist/demo/environmentShowcase.js +0 -15
  161. package/dist/demo/mapDemo.d.ts +0 -2
  162. package/dist/demo/mapDemo.js +0 -206
  163. package/dist/demo/pointerDemo.d.ts +0 -2
  164. package/dist/demo/pointerDemo.js +0 -151
  165. package/dist/demo/sensorShowcase.d.ts +0 -2
  166. package/dist/demo/sensorShowcase.js +0 -131
  167. package/dist/demo/survivalDemo.d.ts +0 -2
  168. 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.7.0",
3
+ "version": "0.9.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.7.0",
38
- "@jgengine/react": "^0.7.0",
39
- "@jgengine/ws": "^0.7.0"
38
+ "@jgengine/core": "^0.9.0",
39
+ "@jgengine/react": "^0.9.0",
40
+ "@jgengine/ws": "^0.9.0"
40
41
  },
41
42
  "peerDependencies": {
42
43
  "@react-three/drei": "^10.0.0",
@@ -1,2 +0,0 @@
1
- import type { PlayableGame } from "../registry.js";
2
- export declare const builderDemoGame: PlayableGame;
@@ -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
- };
@@ -1,2 +0,0 @@
1
- import type { PlayableGame } from "../registry.js";
2
- export declare const demoGame: PlayableGame;
@@ -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,2 +0,0 @@
1
- import type { PlayableGame } from "../registry.js";
2
- export declare const environmentShowcaseGame: PlayableGame;
@@ -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
- };
@@ -1,2 +0,0 @@
1
- import type { PlayableGame } from "../registry.js";
2
- export declare const mapDemoGame: PlayableGame;