@jgengine/shell 0.10.0 → 0.11.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 +13 -2
- package/LICENSE +203 -661
- package/README.md +1 -1
- package/dist/GamePhaseStamp.d.ts +1 -0
- package/dist/GamePhaseStamp.js +9 -0
- package/dist/GamePlayerShell.d.ts +0 -12
- package/dist/GamePlayerShell.js +88 -1043
- package/dist/GameUiPreview.js +0 -2
- package/dist/audio/audioEngine.js +30 -8
- package/dist/audio/audioWire.d.ts +22 -0
- package/dist/audio/audioWire.js +19 -0
- package/dist/behaviour.d.ts +2 -1
- package/dist/behaviour.js +2 -1
- package/dist/behaviourAttach.d.ts +1 -0
- package/dist/behaviourAttach.js +1 -0
- package/dist/behaviourDriver.d.ts +1 -0
- package/dist/behaviourDriver.js +1 -0
- package/dist/boundActionDispatch.d.ts +13 -0
- package/dist/boundActionDispatch.js +37 -0
- package/dist/camera/GameCameraRig.d.ts +5 -2
- package/dist/camera/GameCameraRig.js +2 -2
- package/dist/camera/GameFirstPersonCamera.d.ts +9 -2
- package/dist/camera/GameFirstPersonCamera.js +8 -3
- package/dist/camera/GameInspectionCamera.d.ts +1 -1
- package/dist/camera/GameInspectionCamera.js +2 -2
- package/dist/camera/GameOrbitCamera.js +24 -9
- package/dist/camera/cameraBlendMath.d.ts +3 -0
- package/dist/camera/cameraBlendMath.js +3 -0
- package/dist/camera/fovPreference.d.ts +6 -1
- package/dist/camera/fovPreference.js +6 -1
- package/dist/camera/index.d.ts +4 -7
- package/dist/camera/index.js +4 -7
- package/dist/camera/inspectionCameraMath.d.ts +7 -2
- package/dist/camera/inspectionCameraMath.js +7 -2
- package/dist/camera/orbitCameraMath.d.ts +36 -6
- package/dist/camera/orbitCameraMath.js +56 -6
- package/dist/camera/rigMath.d.ts +94 -24
- package/dist/camera/rigMath.js +100 -24
- package/dist/camera/rigResolve.d.ts +4 -2
- package/dist/camera/rigResolve.js +4 -2
- package/dist/camera/shakeChannel.d.ts +6 -2
- package/dist/camera/shakeChannel.js +6 -2
- package/dist/camera/shakeChannelMath.d.ts +1 -0
- package/dist/camera/shakeChannelMath.js +1 -0
- package/dist/cartridge/runtime.d.ts +15 -0
- package/dist/cartridge/runtime.js +556 -0
- package/dist/cartridge/spec.d.ts +188 -0
- package/dist/cartridge/spec.js +19 -0
- package/dist/cartridge/validate.d.ts +2 -0
- package/dist/cartridge/validate.js +78 -0
- package/dist/cartridge.d.ts +1 -1
- package/dist/cartridge.js +3 -3
- package/dist/commandSink.d.ts +8 -3
- package/dist/commandSink.js +8 -3
- package/dist/defineGame.js +25 -6
- package/dist/devtools/ColPanel.d.ts +1 -0
- package/dist/devtools/ColPanel.js +14 -0
- package/dist/devtools/DevtoolsOverlay.d.ts +7 -3
- package/dist/devtools/DevtoolsOverlay.js +19 -450
- package/dist/devtools/KeysPanel.d.ts +4 -0
- package/dist/devtools/KeysPanel.js +31 -0
- package/dist/devtools/LogsPanel.d.ts +1 -0
- package/dist/devtools/LogsPanel.js +12 -0
- package/dist/devtools/NetPanel.d.ts +4 -0
- package/dist/devtools/NetPanel.js +10 -0
- package/dist/devtools/PerfPanel.d.ts +4 -0
- package/dist/devtools/PerfPanel.js +53 -0
- package/dist/devtools/TunePanel.d.ts +3 -0
- package/dist/devtools/TunePanel.js +309 -0
- package/dist/devtools/agentBridge.d.ts +41 -0
- package/dist/devtools/agentBridge.js +262 -0
- package/dist/devtools/collisionDebug.d.ts +6 -0
- package/dist/devtools/collisionDebug.js +6 -0
- package/dist/devtools/collisionDebugMath.d.ts +8 -2
- package/dist/devtools/collisionDebugMath.js +24 -5
- package/dist/devtools/devtoolsOverrides.d.ts +6 -0
- package/dist/devtools/devtoolsOverrides.js +48 -0
- package/dist/devtools/panelAtoms.d.ts +9 -0
- package/dist/devtools/panelAtoms.js +10 -0
- package/dist/devtools/perfDiagnose.d.ts +2 -0
- package/dist/devtools/perfDiagnose.js +43 -0
- package/dist/diagnostics/RuntimeDiagnostics.d.ts +29 -0
- package/dist/diagnostics/RuntimeDiagnostics.js +69 -0
- package/dist/drivers/FrameDriver.d.ts +37 -0
- package/dist/drivers/FrameDriver.js +232 -0
- package/dist/drivers/HudOnlyDriver.d.ts +16 -0
- package/dist/drivers/HudOnlyDriver.js +60 -0
- package/dist/environment/Daylight.d.ts +23 -7
- package/dist/environment/Daylight.js +106 -13
- package/dist/environment/EnvironmentScene.js +32 -6
- package/dist/environment/VolumetricClouds.d.ts +17 -0
- package/dist/environment/VolumetricClouds.js +202 -0
- package/dist/environment/daylightCycle.d.ts +3 -1
- package/dist/environment/daylightCycle.js +3 -1
- package/dist/environment/groundPadMath.d.ts +3 -0
- package/dist/environment/groundPadMath.js +3 -0
- package/dist/environment/index.d.ts +1 -0
- package/dist/environment/index.js +1 -0
- package/dist/environment/skyLightingPolicy.d.ts +2 -0
- package/dist/environment/skyLightingPolicy.js +2 -0
- package/dist/hotbarActions.d.ts +26 -0
- package/dist/hotbarActions.js +27 -0
- package/dist/inputSink.d.ts +16 -4
- package/dist/inputSink.js +57 -7
- package/dist/materialOverride.d.ts +10 -0
- package/dist/materialOverride.js +15 -3
- package/dist/multiplayer.js +8 -2
- package/dist/postfx/PostProcessing.js +47 -9
- package/dist/postfx/gradeShader.js +9 -1
- package/dist/registry.d.ts +3 -2
- package/dist/render/EnvironmentLighting.d.ts +10 -0
- package/dist/render/EnvironmentLighting.js +34 -0
- package/dist/render/SceneLighting.d.ts +7 -0
- package/dist/render/SceneLighting.js +18 -0
- package/dist/render/SceneModels.d.ts +14 -0
- package/dist/render/SceneModels.js +337 -0
- package/dist/render/defaultSurface.d.ts +29 -0
- package/dist/render/defaultSurface.js +101 -0
- package/dist/render/modelLoad.d.ts +24 -0
- package/dist/render/modelLoad.js +73 -0
- package/dist/render/modelRender.d.ts +11 -0
- package/dist/render/modelRender.js +15 -0
- package/dist/render/resolveModel.d.ts +39 -3
- package/dist/render/resolveModel.js +79 -2
- package/dist/render/useEntityRenderCues.d.ts +13 -0
- package/dist/render/useEntityRenderCues.js +62 -0
- package/dist/scatter/InstancedScatter.d.ts +24 -0
- package/dist/scatter/InstancedScatter.js +130 -0
- package/dist/scatter/scatterModels.d.ts +25 -0
- package/dist/scatter/scatterModels.js +61 -0
- package/dist/scatter/scatterProxies.d.ts +6 -0
- package/dist/scatter/scatterProxies.js +94 -0
- package/dist/scatter.d.ts +1 -0
- package/dist/scatter.js +1 -0
- package/dist/scene/AuthoredScene.d.ts +82 -0
- package/dist/scene/AuthoredScene.js +173 -0
- package/dist/scene/GeneratedAssetRenderer.d.ts +14 -0
- package/dist/scene/GeneratedAssetRenderer.js +59 -0
- package/dist/scene/GrassFieldRenderer.d.ts +2 -0
- package/dist/scene/GrassFieldRenderer.js +25 -0
- package/dist/scene/SoilPatchRenderer.d.ts +2 -0
- package/dist/scene/SoilPatchRenderer.js +21 -0
- package/dist/scene/StudioStage.d.ts +37 -0
- package/dist/scene/StudioStage.js +42 -0
- package/dist/scene/WaterRenderer.d.ts +2 -0
- package/dist/scene/WaterRenderer.js +37 -0
- package/dist/scene/builtinSceneKindRenderers.d.ts +2 -0
- package/dist/scene/builtinSceneKindRenderers.js +22 -0
- package/dist/scene/sceneKindRenderers.d.ts +28 -0
- package/dist/scene/sceneKindRenderers.js +17 -0
- package/dist/scene.d.ts +1 -0
- package/dist/scene.js +1 -0
- package/dist/settings/appliedSettings.d.ts +5 -0
- package/dist/settings/appliedSettings.js +9 -0
- package/dist/settings/settingsController.d.ts +2 -0
- package/dist/settings/settingsController.js +22 -1
- package/dist/shellConstants.d.ts +4 -0
- package/dist/shellConstants.js +4 -0
- package/dist/shellMovement.d.ts +8 -0
- package/dist/shellMovement.js +11 -0
- package/dist/shellPointer.d.ts +20 -0
- package/dist/shellPointer.js +28 -0
- package/dist/structures/PlacementGhost.d.ts +4 -0
- package/dist/structures/PlacementGhost.js +4 -0
- package/dist/structures/TransformGizmo.d.ts +43 -0
- package/dist/structures/TransformGizmo.js +55 -0
- package/dist/structures/index.d.ts +1 -0
- package/dist/structures/index.js +1 -0
- package/dist/terrain/GrassField.js +1 -1
- package/dist/terrain/TerraformBrushCursor.js +3 -0
- package/dist/terrain/grassBudget.d.ts +11 -2
- package/dist/terrain/grassBudget.js +20 -4
- package/dist/terrain/grassGeometry.d.ts +3 -0
- package/dist/terrain/grassGeometry.js +3 -0
- package/dist/terrain/grassMaterial.d.ts +2 -0
- package/dist/terrain/grassMaterial.js +2 -0
- package/dist/terrain/random.d.ts +3 -0
- package/dist/terrain/random.js +3 -0
- package/dist/terrain/soilPatchMaterial.d.ts +10 -0
- package/dist/terrain/soilPatchMaterial.js +109 -0
- package/dist/terrain/terrainDetailMaterial.d.ts +19 -2
- package/dist/terrain/terrainDetailMaterial.js +72 -5
- package/dist/terrain/terrainMath.d.ts +8 -1
- package/dist/terrain/terrainMath.js +8 -1
- package/dist/touch/TouchControlsOverlay.d.ts +9 -5
- package/dist/touch/TouchControlsOverlay.js +201 -22
- package/dist/useShellMultiplayerSync.d.ts +9 -0
- package/dist/useShellMultiplayerSync.js +98 -0
- package/dist/visibility/CullingProvider.js +1 -1
- package/dist/water/OceanConfig.d.ts +2 -0
- package/dist/water/OceanConfig.js +2 -0
- package/dist/weather/fireSpreadPose.d.ts +1 -0
- package/dist/weather/fireSpreadPose.js +1 -0
- package/dist/weather/weatherGeometry.d.ts +1 -0
- package/dist/weather/weatherGeometry.js +1 -0
- package/dist/weather/weatherMath.d.ts +3 -0
- package/dist/weather/weatherMath.js +3 -0
- package/dist/weather/weatherUniforms.d.ts +3 -0
- package/dist/weather/weatherUniforms.js +3 -0
- package/dist/world/RetainedVfx.d.ts +10 -0
- package/dist/world/RetainedVfx.js +136 -0
- package/dist/world/WorldHud.d.ts +49 -2
- package/dist/world/WorldHud.js +87 -2
- package/dist/world/WorldScene.d.ts +21 -0
- package/dist/world/WorldScene.js +159 -0
- package/dist/world/WorldVfx.d.ts +9 -0
- package/dist/world/WorldVfx.js +184 -0
- package/dist/world/entityPose.d.ts +2 -0
- package/dist/world/entityPose.js +2 -0
- package/dist/world/floatTextStyle.d.ts +1 -0
- package/dist/world/floatTextStyle.js +1 -0
- package/dist/world/telegraphPulse.d.ts +1 -0
- package/dist/world/telegraphPulse.js +1 -0
- package/dist/world/worldBarSamples.d.ts +25 -0
- package/dist/world/worldBarSamples.js +38 -0
- package/dist/worldSky.d.ts +5 -0
- package/dist/worldSky.js +11 -0
- package/dist/worldSync.d.ts +2 -1
- package/dist/worldSync.js +2 -1
- package/package.json +6 -6
- package/skills/jgengine/SKILL.md +51 -0
- package/skills/jgengine/api.md +434 -0
- package/skills/jgengine/capabilities.md +29 -0
- package/skills/jgengine/reference-cartridge.md +72 -0
- package/skills/jgengine-ui/SKILL.md +36 -0
- package/skills/jgengine-ui/api.md +1852 -0
- package/skills/jgengine-ui/capabilities.md +142 -0
- package/skills/jgengine-ui/reference.md +500 -0
- package/skills/jgengine-ui/references/visual-scorecard.md +73 -0
- package/skills/jgengine-world/SKILL.md +38 -0
- package/skills/jgengine-world/api.md +2652 -0
- package/skills/jgengine-world/capabilities.md +370 -0
- package/skills/jgengine-world/reference.md +222 -0
- package/llms.txt +0 -2761
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useFrame } from "@react-three/fiber";
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import * as THREE from "three";
|
|
5
|
+
import { useGameContext } from "@jgengine/react/provider";
|
|
6
|
+
const PARTICLE_COUNT = {
|
|
7
|
+
projectile: 7,
|
|
8
|
+
beam: 6,
|
|
9
|
+
nova: 14,
|
|
10
|
+
glow: 10,
|
|
11
|
+
spark: 12,
|
|
12
|
+
};
|
|
13
|
+
function planarDir() {
|
|
14
|
+
const angle = Math.random() * Math.PI * 2;
|
|
15
|
+
return new THREE.Vector3(Math.cos(angle), 0, Math.sin(angle));
|
|
16
|
+
}
|
|
17
|
+
function sphereDir() {
|
|
18
|
+
return new THREE.Vector3(Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1)
|
|
19
|
+
.normalize();
|
|
20
|
+
}
|
|
21
|
+
function makeParticles(kind) {
|
|
22
|
+
const count = PARTICLE_COUNT[kind];
|
|
23
|
+
const out = [];
|
|
24
|
+
for (let i = 0; i < count; i += 1) {
|
|
25
|
+
if (kind === "nova") {
|
|
26
|
+
out.push({ offset: new THREE.Vector3(), dir: planarDir(), speed: 1, size: 0.5 + Math.random() * 0.4, delay: 0 });
|
|
27
|
+
}
|
|
28
|
+
else if (kind === "spark") {
|
|
29
|
+
const dir = sphereDir();
|
|
30
|
+
dir.y = Math.abs(dir.y) * 0.6 + 0.2;
|
|
31
|
+
out.push({ offset: new THREE.Vector3(), dir, speed: 2.4 + Math.random() * 1.8, size: 0.28 + Math.random() * 0.22, delay: 0 });
|
|
32
|
+
}
|
|
33
|
+
else if (kind === "glow") {
|
|
34
|
+
out.push({
|
|
35
|
+
offset: planarDir().multiplyScalar(0.35 + Math.random() * 0.5),
|
|
36
|
+
dir: new THREE.Vector3(0, 1, 0),
|
|
37
|
+
speed: 0.5 + Math.random() * 0.6,
|
|
38
|
+
size: 0.55 + Math.random() * 0.5,
|
|
39
|
+
delay: Math.random() * 0.3,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
else if (kind === "beam") {
|
|
43
|
+
out.push({ offset: new THREE.Vector3(), dir: new THREE.Vector3(), speed: 0, size: 0.45 + Math.random() * 0.3, delay: 0 });
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
out.push({
|
|
47
|
+
offset: sphereDir().multiplyScalar(0.12),
|
|
48
|
+
dir: new THREE.Vector3(),
|
|
49
|
+
speed: 0,
|
|
50
|
+
size: 0.4 + Math.random() * 0.25,
|
|
51
|
+
delay: i * 0.05,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
function useRadialTexture() {
|
|
58
|
+
return useMemo(() => {
|
|
59
|
+
const size = 64;
|
|
60
|
+
const canvas = document.createElement("canvas");
|
|
61
|
+
canvas.width = size;
|
|
62
|
+
canvas.height = size;
|
|
63
|
+
const c = canvas.getContext("2d");
|
|
64
|
+
if (c !== null) {
|
|
65
|
+
const gradient = c.createRadialGradient(size / 2, size / 2, 0, size / 2, size / 2, size / 2);
|
|
66
|
+
gradient.addColorStop(0, "rgba(255,255,255,1)");
|
|
67
|
+
gradient.addColorStop(0.35, "rgba(255,255,255,0.65)");
|
|
68
|
+
gradient.addColorStop(1, "rgba(255,255,255,0)");
|
|
69
|
+
c.fillStyle = gradient;
|
|
70
|
+
c.fillRect(0, 0, size, size);
|
|
71
|
+
}
|
|
72
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
73
|
+
texture.needsUpdate = true;
|
|
74
|
+
return texture;
|
|
75
|
+
}, []);
|
|
76
|
+
}
|
|
77
|
+
function clamp01(value) {
|
|
78
|
+
return value < 0 ? 0 : value > 1 ? 1 : value;
|
|
79
|
+
}
|
|
80
|
+
function VfxItem({ event, texture }) {
|
|
81
|
+
const born = useRef(performance.now());
|
|
82
|
+
const sprites = useRef([]);
|
|
83
|
+
const ringRef = useRef(null);
|
|
84
|
+
const ringMat = useRef(null);
|
|
85
|
+
const beamMat = useRef(null);
|
|
86
|
+
const color = useMemo(() => new THREE.Color(event.color), [event.color]);
|
|
87
|
+
const from = useMemo(() => new THREE.Vector3(event.from[0], event.from[1] + 1, event.from[2]), [event.from]);
|
|
88
|
+
const to = useMemo(() => (event.to === undefined ? from.clone() : new THREE.Vector3(event.to[0], event.to[1] + 1, event.to[2])), [event.to, from]);
|
|
89
|
+
const radius = event.radius ?? 2.5;
|
|
90
|
+
const beam = useMemo(() => {
|
|
91
|
+
const delta = new THREE.Vector3().subVectors(to, from);
|
|
92
|
+
const length = Math.max(0.001, delta.length());
|
|
93
|
+
const mid = new THREE.Vector3().addVectors(from, to).multiplyScalar(0.5);
|
|
94
|
+
const quat = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), delta.clone().normalize());
|
|
95
|
+
return { length, mid, quat };
|
|
96
|
+
}, [from, to]);
|
|
97
|
+
const particles = useMemo(() => makeParticles(event.kind), [event.kind]);
|
|
98
|
+
const scratch = useRef(new THREE.Vector3());
|
|
99
|
+
useFrame(() => {
|
|
100
|
+
const p = clamp01((performance.now() - born.current) / event.durationMs);
|
|
101
|
+
const kind = event.kind;
|
|
102
|
+
for (let i = 0; i < particles.length; i += 1) {
|
|
103
|
+
const sprite = sprites.current[i];
|
|
104
|
+
const particle = particles[i];
|
|
105
|
+
if (sprite === undefined || sprite === null || particle === undefined)
|
|
106
|
+
continue;
|
|
107
|
+
const pos = scratch.current;
|
|
108
|
+
let opacity = 0;
|
|
109
|
+
let scale = particle.size;
|
|
110
|
+
if (kind === "projectile") {
|
|
111
|
+
const travel = clamp01(p * 1.15 - particle.delay);
|
|
112
|
+
pos.copy(from).lerp(to, travel).add(particle.offset);
|
|
113
|
+
opacity = (1 - p) * (i === 0 ? 1 : 0.55);
|
|
114
|
+
scale = particle.size * (i === 0 ? 1 : 0.7);
|
|
115
|
+
}
|
|
116
|
+
else if (kind === "beam") {
|
|
117
|
+
pos.copy(from).lerp(to, i / Math.max(1, particles.length - 1));
|
|
118
|
+
opacity = (1 - p) * 0.8;
|
|
119
|
+
}
|
|
120
|
+
else if (kind === "nova") {
|
|
121
|
+
pos.set(event.from[0], event.from[1] + 0.4, event.from[2]).addScaledVector(particle.dir, radius * p);
|
|
122
|
+
opacity = (1 - p) * 0.9;
|
|
123
|
+
}
|
|
124
|
+
else if (kind === "glow") {
|
|
125
|
+
const local = clamp01(p - particle.delay);
|
|
126
|
+
pos.copy(from).add(particle.offset).addScaledVector(particle.dir, particle.speed * local);
|
|
127
|
+
opacity = Math.sin(clamp01(p) * Math.PI) * 0.9;
|
|
128
|
+
scale = particle.size * (0.8 + 0.3 * Math.sin(local * Math.PI));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
pos.copy(to).addScaledVector(particle.dir, particle.speed * p);
|
|
132
|
+
pos.y -= p * p * 1.4;
|
|
133
|
+
opacity = (1 - p) * 0.95;
|
|
134
|
+
}
|
|
135
|
+
sprite.position.copy(pos);
|
|
136
|
+
sprite.scale.set(scale, scale, 1);
|
|
137
|
+
const material = sprite.material;
|
|
138
|
+
material.opacity = opacity;
|
|
139
|
+
}
|
|
140
|
+
if (ringRef.current !== null && ringMat.current !== null) {
|
|
141
|
+
const s = Math.max(0.001, radius * p);
|
|
142
|
+
ringRef.current.scale.set(s, s, s);
|
|
143
|
+
ringMat.current.opacity = (1 - p) * 0.7;
|
|
144
|
+
}
|
|
145
|
+
if (beamMat.current !== null) {
|
|
146
|
+
beamMat.current.opacity = (1 - p) * 0.85;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return (_jsxs("group", { children: [particles.map((_, i) => (_jsx("sprite", { ref: (node) => {
|
|
150
|
+
sprites.current[i] = node;
|
|
151
|
+
}, children: _jsx("spriteMaterial", { map: texture, color: color, transparent: true, depthWrite: false, depthTest: false, blending: THREE.AdditiveBlending, toneMapped: false, opacity: 0 }) }, i))), event.kind === "nova" && (_jsxs("mesh", { ref: ringRef, position: [event.from[0], event.from[1] + 0.06, event.from[2]], rotation: [-Math.PI / 2, 0, 0], renderOrder: 998, children: [_jsx("ringGeometry", { args: [0.82, 1, 48] }), _jsx("meshBasicMaterial", { ref: ringMat, color: color, transparent: true, side: THREE.DoubleSide, depthWrite: false, depthTest: false, blending: THREE.AdditiveBlending, toneMapped: false, opacity: 0 })] })), event.kind === "beam" && (_jsxs("mesh", { position: [beam.mid.x, beam.mid.y, beam.mid.z], quaternion: [beam.quat.x, beam.quat.y, beam.quat.z, beam.quat.w], renderOrder: 998, children: [_jsx("cylinderGeometry", { args: [0.12, 0.12, beam.length, 8, 1, true] }), _jsx("meshBasicMaterial", { ref: beamMat, color: color, transparent: true, side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending, toneMapped: false, opacity: 0 })] }))] }));
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Renders `combat.vfx` bursts as additive sprite-particle effects in the world scene. Auto-mounted by the
|
|
155
|
+
* player shell alongside the other world overlays; games emit effects via `ctx.scene.entity.vfx(...)`.
|
|
156
|
+
*
|
|
157
|
+
* @internal
|
|
158
|
+
*/
|
|
159
|
+
export function WorldSpellVfx({ tailMs = 200 }) {
|
|
160
|
+
const ctx = useGameContext();
|
|
161
|
+
const texture = useRadialTexture();
|
|
162
|
+
const [active, setActive] = useState([]);
|
|
163
|
+
const key = useRef(0);
|
|
164
|
+
const timers = useRef(new Set());
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
const pending = timers.current;
|
|
167
|
+
const off = ctx.game.events.on("combat.vfx", (event) => {
|
|
168
|
+
const entry = { key: key.current++, event };
|
|
169
|
+
setActive((current) => [...current, entry]);
|
|
170
|
+
const timer = setTimeout(() => {
|
|
171
|
+
pending.delete(timer);
|
|
172
|
+
setActive((current) => current.filter((item) => item.key !== entry.key));
|
|
173
|
+
}, event.durationMs + tailMs);
|
|
174
|
+
pending.add(timer);
|
|
175
|
+
});
|
|
176
|
+
return () => {
|
|
177
|
+
off();
|
|
178
|
+
for (const timer of pending)
|
|
179
|
+
clearTimeout(timer);
|
|
180
|
+
pending.clear();
|
|
181
|
+
};
|
|
182
|
+
}, [ctx, tailMs]);
|
|
183
|
+
return (_jsx(_Fragment, { children: active.map((item) => (_jsx(VfxItem, { event: item.event, texture: texture }, item.key))) }));
|
|
184
|
+
}
|
|
@@ -10,5 +10,7 @@ export interface PoseSource {
|
|
|
10
10
|
position: readonly [number, number, number];
|
|
11
11
|
rotationY: number;
|
|
12
12
|
}
|
|
13
|
+
/** @internal */
|
|
13
14
|
export declare function writeEntityPose(target: PoseWritable, source: PoseSource): void;
|
|
15
|
+
/** @internal */
|
|
14
16
|
export declare function posesEqual(a: PoseSource, b: PoseSource): boolean;
|
package/dist/world/entityPose.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
/** @internal */
|
|
1
2
|
export function writeEntityPose(target, source) {
|
|
2
3
|
target.position.set(source.position[0], source.position[1], source.position[2]);
|
|
3
4
|
target.rotation.y = source.rotationY;
|
|
4
5
|
}
|
|
6
|
+
/** @internal */
|
|
5
7
|
export function posesEqual(a, b) {
|
|
6
8
|
return (a.position[0] === b.position[0] &&
|
|
7
9
|
a.position[1] === b.position[1] &&
|
|
@@ -16,6 +16,7 @@ export interface Projectable {
|
|
|
16
16
|
y: number;
|
|
17
17
|
z: number;
|
|
18
18
|
}
|
|
19
|
+
/** @internal */
|
|
19
20
|
export declare function collectWorldBarSamples(ctx: GameContext, statId: string, height: number, roles: readonly CatalogEntityRole[] | undefined, resolveRole: ((entity: SceneEntity) => CatalogEntityRole | undefined) | undefined, camera: {
|
|
20
21
|
matrixWorldInverse: unknown;
|
|
21
22
|
projectionMatrix: unknown;
|
|
@@ -23,6 +24,30 @@ export declare function collectWorldBarSamples(ctx: GameContext, statId: string,
|
|
|
23
24
|
width: number;
|
|
24
25
|
height: number;
|
|
25
26
|
}, into: WorldBarSample[], project: Projectable, maxDistance?: number): number;
|
|
27
|
+
/** One entity's projected nameplate: screen `x`/`y`, display `name`, health `percent` (or `null` when statless), and world `distance` from the player. */
|
|
28
|
+
export interface NameplateSample {
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
x: number;
|
|
32
|
+
y: number;
|
|
33
|
+
/** Health-stat fraction 0..1, or `null` when the entity carries no `statId` stat (name-only nameplate). */
|
|
34
|
+
percent: number | null;
|
|
35
|
+
distance: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Projects every non-local, role/distance-filtered entity into `into` as a
|
|
39
|
+
* `NameplateSample`. Pure data step powering `WorldNameplates` — swap in your
|
|
40
|
+
* own renderer by calling this directly instead of the component.
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
export declare function collectNameplateSamples(ctx: GameContext, statId: string, height: number, roles: readonly CatalogEntityRole[] | undefined, resolveRole: ((entity: SceneEntity) => CatalogEntityRole | undefined) | undefined, camera: {
|
|
44
|
+
matrixWorldInverse: unknown;
|
|
45
|
+
projectionMatrix: unknown;
|
|
46
|
+
}, viewport: {
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
}, into: NameplateSample[], project: Projectable, maxDistance?: number): number;
|
|
50
|
+
/** @internal */
|
|
26
51
|
export declare function paintWorldBarSamples(canvas: {
|
|
27
52
|
width: number;
|
|
28
53
|
height: number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { worldHealthBarAllowsRole } from "@jgengine/core/game/playableGame";
|
|
2
|
+
/** @internal */
|
|
2
3
|
export function collectWorldBarSamples(ctx, statId, height, roles, resolveRole, camera, viewport, into, project, maxDistance = 60) {
|
|
3
4
|
into.length = 0;
|
|
4
5
|
const playerId = ctx.player.userId;
|
|
@@ -28,6 +29,43 @@ export function collectWorldBarSamples(ctx, statId, height, roles, resolveRole,
|
|
|
28
29
|
}
|
|
29
30
|
return into.length;
|
|
30
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Projects every non-local, role/distance-filtered entity into `into` as a
|
|
34
|
+
* `NameplateSample`. Pure data step powering `WorldNameplates` — swap in your
|
|
35
|
+
* own renderer by calling this directly instead of the component.
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export function collectNameplateSamples(ctx, statId, height, roles, resolveRole, camera, viewport, into, project, maxDistance = 40) {
|
|
39
|
+
into.length = 0;
|
|
40
|
+
const playerId = ctx.player.userId;
|
|
41
|
+
const player = ctx.scene.entity.get(playerId);
|
|
42
|
+
for (const entity of ctx.scene.entity.list()) {
|
|
43
|
+
if (entity.id === playerId)
|
|
44
|
+
continue;
|
|
45
|
+
if (!worldHealthBarAllowsRole(roles, resolveRole?.(entity)))
|
|
46
|
+
continue;
|
|
47
|
+
const distance = player === null
|
|
48
|
+
? 0
|
|
49
|
+
: Math.hypot(entity.position[0] - player.position[0], entity.position[2] - player.position[2]);
|
|
50
|
+
if (player !== null && distance > maxDistance)
|
|
51
|
+
continue;
|
|
52
|
+
const stat = ctx.scene.entity.stats.get(entity.id, statId);
|
|
53
|
+
let percent = null;
|
|
54
|
+
if (stat !== null) {
|
|
55
|
+
const range = stat.max - stat.min;
|
|
56
|
+
percent = range <= 0 ? 0 : Math.max(0, Math.min(1, (stat.current - stat.min) / range));
|
|
57
|
+
}
|
|
58
|
+
project.set(entity.position[0], entity.position[1] + height, entity.position[2]);
|
|
59
|
+
project.project(camera);
|
|
60
|
+
if (project.z < -1 || project.z > 1)
|
|
61
|
+
continue;
|
|
62
|
+
const x = (project.x * 0.5 + 0.5) * viewport.width;
|
|
63
|
+
const y = (-project.y * 0.5 + 0.5) * viewport.height;
|
|
64
|
+
into.push({ id: entity.id, name: entity.name, x, y, percent, distance });
|
|
65
|
+
}
|
|
66
|
+
return into.length;
|
|
67
|
+
}
|
|
68
|
+
/** @internal */
|
|
31
69
|
export function paintWorldBarSamples(canvas, samples, dpr, barWidthPx = 112, barHeightPx = 10) {
|
|
32
70
|
const g = canvas.getContext("2d");
|
|
33
71
|
if (g === null)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SkyEnvironmentDescriptor, WorldFeature } from "@jgengine/core/world/features";
|
|
2
|
+
/** The world's declared sky, when its world feature is an environment with one (#196.1). @internal */
|
|
3
|
+
export declare function resolveWorldSky(world: WorldFeature | undefined): SkyEnvironmentDescriptor | undefined;
|
|
4
|
+
/** Deterministic HSL color from a stable instance id (default entity/object tint). @internal */
|
|
5
|
+
export declare function colorFromId(id: string): string;
|
package/dist/worldSky.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** The world's declared sky, when its world feature is an environment with one (#196.1). @internal */
|
|
2
|
+
export function resolveWorldSky(world) {
|
|
3
|
+
return world?.kind === "environment" ? world.sky : undefined;
|
|
4
|
+
}
|
|
5
|
+
/** Deterministic HSL color from a stable instance id (default entity/object tint). @internal */
|
|
6
|
+
export function colorFromId(id) {
|
|
7
|
+
let hash = 0;
|
|
8
|
+
for (let index = 0; index < id.length; index += 1)
|
|
9
|
+
hash = (hash * 31 + id.charCodeAt(index)) >>> 0;
|
|
10
|
+
return `hsl(${hash % 360}, 65%, 55%)`;
|
|
11
|
+
}
|
package/dist/worldSync.d.ts
CHANGED
|
@@ -6,5 +6,6 @@ import type { GameRuntimeFeeds } from "@jgengine/core/runtime/transport";
|
|
|
6
6
|
* of a locally-simulated one. Pure and transport-agnostic — the backend's `feeds.subscribeServer` is the only
|
|
7
7
|
* dependency; returns the unsubscribe. The shell attaches this (and gates its local sim) when the game's adapter
|
|
8
8
|
* opts into `authority: "server"`.
|
|
9
|
-
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
10
11
|
export declare function attachWorldSync(feeds: Pick<GameRuntimeFeeds, "subscribeServer">, serverId: string, ctx: Pick<GameContext, "hydrate">): () => void;
|
package/dist/worldSync.js
CHANGED
|
@@ -5,7 +5,8 @@ import { createWorldMirror } from "@jgengine/core/runtime/worldMirror";
|
|
|
5
5
|
* of a locally-simulated one. Pure and transport-agnostic — the backend's `feeds.subscribeServer` is the only
|
|
6
6
|
* dependency; returns the unsubscribe. The shell attaches this (and gates its local sim) when the game's adapter
|
|
7
7
|
* opts into `authority: "server"`.
|
|
8
|
-
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
9
10
|
export function attachWorldSync(feeds, serverId, ctx) {
|
|
10
11
|
const mirror = createWorldMirror(ctx);
|
|
11
12
|
return feeds.subscribeServer(serverId, (view) => {
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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
|
-
"license": "
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": false,
|
|
8
8
|
"repository": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"files": [
|
|
14
14
|
"dist",
|
|
15
15
|
"CHANGELOG.md",
|
|
16
|
-
"
|
|
16
|
+
"skills"
|
|
17
17
|
],
|
|
18
18
|
"exports": {
|
|
19
19
|
"./weather": {
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"test": "bun test src"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@jgengine/core": "^0.
|
|
39
|
-
"@jgengine/react": "^0.
|
|
40
|
-
"@jgengine/ws": "^0.
|
|
38
|
+
"@jgengine/core": "^0.11.0",
|
|
39
|
+
"@jgengine/react": "^0.11.0",
|
|
40
|
+
"@jgengine/ws": "^0.11.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@react-three/drei": "^10.0.0",
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jgengine
|
|
3
|
+
description: Route game work to the smallest useful JGengine guidance set.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# JGengine intake and router
|
|
7
|
+
|
|
8
|
+
Read the repository [README](../../../README.md) for package truth, commands, layering, and license. This skill decides what to load; it is not an engine manual.
|
|
9
|
+
|
|
10
|
+
## 1. Establish the target
|
|
11
|
+
|
|
12
|
+
Capture only decisions that change architecture:
|
|
13
|
+
|
|
14
|
+
- player point of view and controls
|
|
15
|
+
- world shape and authored content
|
|
16
|
+
- core loop, interaction, combat, and progression
|
|
17
|
+
- solo, local, hosted, or persistent multiplayer
|
|
18
|
+
- HUD/menu states and art direction
|
|
19
|
+
- one observable completion scenario
|
|
20
|
+
|
|
21
|
+
For a new in-repo game, scaffold with `bun run new:game <id> --name "Title"`. Outside this monorepo, start with `npx jgengine create`; never copy `Games/*`.
|
|
22
|
+
|
|
23
|
+
## 2. Select domains
|
|
24
|
+
|
|
25
|
+
Load only rows the target needs. Do not preload every domain.
|
|
26
|
+
|
|
27
|
+
| Need | Skill | Owner |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| scene placement, terrain painting, paths, asset authoring | `jgengine-editor` | editor document and authoring tools |
|
|
30
|
+
| runtime world, movement, input, interaction, AI, navigation, audio | `jgengine-world` | spatial simulation and world runtime |
|
|
31
|
+
| game state, systems, inventory, crafting, quests, economy, turns | `jgengine-gameplay` | non-combat rules and state |
|
|
32
|
+
| targeting, stats, damage, effects, abilities, projectiles, loot | `jgengine-combat` | combat resolution |
|
|
33
|
+
| HUD, menus, layout, accessibility, touch presentation | `jgengine-ui` | player-facing interface |
|
|
34
|
+
| models, sprites, materials, audio files, source licensing | `jgengine-assets` | asset discovery and catalogs |
|
|
35
|
+
| authority, transport, replication, sessions, host persistence | `jgengine-multiplayer` | network topology and persistence adapters |
|
|
36
|
+
|
|
37
|
+
Scenes normally require both `jgengine-editor` for authoring and `jgengine-world` for runtime consumption. Combat and multiplayer are opt-in, not default intake.
|
|
38
|
+
|
|
39
|
+
## 3. Discover before designing
|
|
40
|
+
|
|
41
|
+
Within each selected domain:
|
|
42
|
+
|
|
43
|
+
1. Search `capabilities.md` by intent; it maps needs to imports.
|
|
44
|
+
2. Open `api.md` only when exact signatures or export inventory are needed.
|
|
45
|
+
3. Open the linked reference only for a deeper recipe or trap.
|
|
46
|
+
|
|
47
|
+
If no capability fits, identify the upstream package seam before writing game-local code. In this repository, implement a reusable primitive with its first adopter; if editor-owned content cannot be expressed, file the editor gap first.
|
|
48
|
+
|
|
49
|
+
## 4. Build and finish
|
|
50
|
+
|
|
51
|
+
Keep scene coordinates in the editor document, state serializable, randomness injected, and hot-path work bounded. Use `jgengine-verify` for evidence appropriate to the change. Inside this monorepo, `workflow` owns issue/PR completion. Visual polish work also uses the `jgengine-ui` quality reference.
|