@jgengine/shell 0.9.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 +34 -3
- 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 +4 -31
- package/dist/GamePlayerShell.js +162 -932
- package/dist/GameUiPreview.js +0 -2
- package/dist/audio/audioEngine.d.ts +8 -0
- package/dist/audio/audioEngine.js +63 -8
- package/dist/audio/audioWire.d.ts +22 -0
- package/dist/audio/audioWire.js +19 -0
- package/dist/audio/musicDirector.d.ts +31 -0
- package/dist/audio/musicDirector.js +125 -0
- package/dist/audio/musicVoices.d.ts +10 -0
- package/dist/audio/musicVoices.js +338 -0
- package/dist/audio/synthEngine.d.ts +9 -0
- package/dist/audio/synthEngine.js +57 -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 +12 -2
- package/dist/camera/GameFirstPersonCamera.js +29 -7
- package/dist/camera/GameInspectionCamera.d.ts +1 -1
- package/dist/camera/GameInspectionCamera.js +13 -2
- package/dist/camera/GameOrbitCamera.js +59 -10
- 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 +54 -6
- package/dist/camera/orbitCameraMath.js +61 -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 +4 -3
- package/dist/commandSink.d.ts +25 -0
- package/dist/commandSink.js +32 -0
- package/dist/defineGame.js +26 -5
- package/dist/devtools/ColPanel.d.ts +1 -0
- package/dist/devtools/ColPanel.js +14 -0
- package/dist/devtools/CollisionDebugWorld.js +1 -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 +9 -2
- package/dist/devtools/collisionDebugMath.js +25 -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 +29 -7
- package/dist/environment/Daylight.js +153 -13
- package/dist/environment/EnvironmentScene.js +42 -7
- package/dist/environment/RoadRibbons.d.ts +7 -0
- package/dist/environment/RoadRibbons.js +57 -0
- 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 +30 -0
- package/dist/inputSink.js +85 -0
- package/dist/materialOverride.d.ts +10 -0
- package/dist/materialOverride.js +15 -3
- package/dist/multiplayer.js +14 -4
- package/dist/postfx/PostProcessing.d.ts +10 -0
- package/dist/postfx/PostProcessing.js +120 -0
- package/dist/postfx/gradeShader.d.ts +4 -0
- package/dist/postfx/gradeShader.js +72 -0
- 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/CarvedTerrain.d.ts +4 -1
- package/dist/terrain/CarvedTerrain.js +4 -3
- 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 +31 -0
- package/dist/terrain/terrainDetailMaterial.js +157 -0
- 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 +211 -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 +52 -3
- package/dist/world/WorldHud.js +94 -9
- 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 +26 -1
- package/dist/world/worldBarSamples.js +45 -1
- package/dist/worldSky.d.ts +5 -0
- package/dist/worldSky.js +11 -0
- package/dist/worldSync.d.ts +11 -0
- package/dist/worldSync.js +20 -0
- 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 -2643
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type { SpawnDirectorConfig } from "@jgengine/core/ai/spawnDirector";
|
|
2
|
+
import type { Curve } from "@jgengine/core/game/progression";
|
|
3
|
+
import type { ActionCodesMap } from "@jgengine/core/input/actionBindings";
|
|
4
|
+
import type { GameContext } from "@jgengine/core/runtime/gameContext";
|
|
5
|
+
export type Leveled = number | {
|
|
6
|
+
base: number;
|
|
7
|
+
perLevel: number;
|
|
8
|
+
min?: number;
|
|
9
|
+
max?: number;
|
|
10
|
+
} | {
|
|
11
|
+
table: readonly number[];
|
|
12
|
+
};
|
|
13
|
+
export declare function leveled(value: Leveled, level: number): number;
|
|
14
|
+
export interface CartridgePlayer {
|
|
15
|
+
kind: string;
|
|
16
|
+
health: number;
|
|
17
|
+
walkSpeed: number;
|
|
18
|
+
spawnAt?: readonly [number, number, number];
|
|
19
|
+
}
|
|
20
|
+
export interface CartridgeEnemy {
|
|
21
|
+
label: string;
|
|
22
|
+
health: number;
|
|
23
|
+
walkSpeed: number;
|
|
24
|
+
xp: number;
|
|
25
|
+
contact: {
|
|
26
|
+
damage: number;
|
|
27
|
+
intervalSeconds: number;
|
|
28
|
+
};
|
|
29
|
+
behavior?: "chase" | "none";
|
|
30
|
+
}
|
|
31
|
+
export interface CartridgeFlow {
|
|
32
|
+
start?: "auto" | "gate";
|
|
33
|
+
countdownSeconds?: number;
|
|
34
|
+
restart?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface WeaponCommon {
|
|
37
|
+
label: string;
|
|
38
|
+
damage: Leveled;
|
|
39
|
+
cooldownMs: Leveled;
|
|
40
|
+
maxLevel: number;
|
|
41
|
+
fxColor?: string;
|
|
42
|
+
fxEmissive?: string;
|
|
43
|
+
}
|
|
44
|
+
export type CartridgeWeapon = (WeaponCommon & {
|
|
45
|
+
kind: "projectile";
|
|
46
|
+
range: number;
|
|
47
|
+
speed: number;
|
|
48
|
+
}) | (WeaponCommon & {
|
|
49
|
+
kind: "orbit";
|
|
50
|
+
blades: Leveled;
|
|
51
|
+
radius: Leveled;
|
|
52
|
+
hitRadius: number;
|
|
53
|
+
angularSpeed: number;
|
|
54
|
+
}) | (WeaponCommon & {
|
|
55
|
+
kind: "pulse";
|
|
56
|
+
radius: Leveled;
|
|
57
|
+
durationSeconds: number;
|
|
58
|
+
}) | (WeaponCommon & {
|
|
59
|
+
kind: "custom";
|
|
60
|
+
fire(ctx: GameContext, run: CartridgeRun, args: WeaponFireArgs): void;
|
|
61
|
+
});
|
|
62
|
+
export interface WeaponFireArgs {
|
|
63
|
+
weaponId: string;
|
|
64
|
+
level: number;
|
|
65
|
+
damage: number;
|
|
66
|
+
playerId: string;
|
|
67
|
+
playerPosition: readonly [number, number, number];
|
|
68
|
+
enemyIds: readonly string[];
|
|
69
|
+
dt: number;
|
|
70
|
+
}
|
|
71
|
+
export type UpgradeEffect = {
|
|
72
|
+
kind: "weaponLevel";
|
|
73
|
+
weapon: string;
|
|
74
|
+
} | {
|
|
75
|
+
kind: "statBonus";
|
|
76
|
+
stat: string;
|
|
77
|
+
amount: number;
|
|
78
|
+
} | {
|
|
79
|
+
kind: "fieldAdd";
|
|
80
|
+
field: string;
|
|
81
|
+
amount: number;
|
|
82
|
+
} | {
|
|
83
|
+
kind: "fieldMultiply";
|
|
84
|
+
field: string;
|
|
85
|
+
factor: number;
|
|
86
|
+
} | {
|
|
87
|
+
kind: "custom";
|
|
88
|
+
apply(ctx: GameContext, run: CartridgeRun, stacks: number): void;
|
|
89
|
+
};
|
|
90
|
+
export interface CartridgeUpgrade {
|
|
91
|
+
id: string;
|
|
92
|
+
label: string;
|
|
93
|
+
weight: number;
|
|
94
|
+
maxStacks: number;
|
|
95
|
+
effect: UpgradeEffect;
|
|
96
|
+
}
|
|
97
|
+
export interface CartridgeProgression {
|
|
98
|
+
xp: Curve;
|
|
99
|
+
maxLevel: number;
|
|
100
|
+
draft: {
|
|
101
|
+
choices: number;
|
|
102
|
+
upgrades: readonly CartridgeUpgrade[];
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export interface CartridgeSpawning {
|
|
106
|
+
director: SpawnDirectorConfig;
|
|
107
|
+
placement: {
|
|
108
|
+
kind: "ring";
|
|
109
|
+
radius: number;
|
|
110
|
+
} | {
|
|
111
|
+
kind: "custom";
|
|
112
|
+
position(ctx: GameContext, run: CartridgeRun): readonly [number, number, number];
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export interface CartridgeXpGems {
|
|
116
|
+
collectRadius: number;
|
|
117
|
+
pullSpeed: number;
|
|
118
|
+
rarityThresholds: readonly (readonly [number, string])[];
|
|
119
|
+
defaultRarity: string;
|
|
120
|
+
}
|
|
121
|
+
export interface CartridgeRules {
|
|
122
|
+
win?: {
|
|
123
|
+
kind: "survive";
|
|
124
|
+
seconds: number;
|
|
125
|
+
} | {
|
|
126
|
+
kind: "custom";
|
|
127
|
+
check(ctx: GameContext, run: CartridgeRun): boolean;
|
|
128
|
+
};
|
|
129
|
+
lose?: {
|
|
130
|
+
kind: "playerDeath";
|
|
131
|
+
} | {
|
|
132
|
+
kind: "custom";
|
|
133
|
+
check(ctx: GameContext, run: CartridgeRun): boolean;
|
|
134
|
+
};
|
|
135
|
+
killLeaderboardStat?: string;
|
|
136
|
+
}
|
|
137
|
+
export interface CartridgeSpec {
|
|
138
|
+
seed?: string | number;
|
|
139
|
+
flow?: CartridgeFlow;
|
|
140
|
+
player: CartridgePlayer;
|
|
141
|
+
enemies: Record<string, CartridgeEnemy>;
|
|
142
|
+
combat: {
|
|
143
|
+
contactRadius: number;
|
|
144
|
+
};
|
|
145
|
+
spawning: CartridgeSpawning;
|
|
146
|
+
weapons: Record<string, CartridgeWeapon>;
|
|
147
|
+
progression: CartridgeProgression;
|
|
148
|
+
xpGems: CartridgeXpGems;
|
|
149
|
+
rules: CartridgeRules;
|
|
150
|
+
fields?: Record<string, number>;
|
|
151
|
+
input?: ActionCodesMap;
|
|
152
|
+
systems?: readonly ((ctx: GameContext, run: CartridgeRun, dt: number) => void)[];
|
|
153
|
+
}
|
|
154
|
+
export interface BoltFx {
|
|
155
|
+
id: number;
|
|
156
|
+
weaponId: string;
|
|
157
|
+
origin: readonly [number, number, number];
|
|
158
|
+
target: readonly [number, number, number];
|
|
159
|
+
firedAt: number;
|
|
160
|
+
travelSeconds: number;
|
|
161
|
+
}
|
|
162
|
+
export interface PulseFx {
|
|
163
|
+
id: number;
|
|
164
|
+
weaponId: string;
|
|
165
|
+
at: readonly [number, number, number];
|
|
166
|
+
firedAt: number;
|
|
167
|
+
durationSeconds: number;
|
|
168
|
+
maxRadius: number;
|
|
169
|
+
}
|
|
170
|
+
export type CartridgePhase = "start" | "countdown" | "playing" | "won" | "lost";
|
|
171
|
+
export interface CartridgeRun {
|
|
172
|
+
phase: CartridgePhase;
|
|
173
|
+
playingSeconds: number;
|
|
174
|
+
countdownRemaining: number;
|
|
175
|
+
kills: number;
|
|
176
|
+
weaponLevel(weaponId: string): number;
|
|
177
|
+
field(name: string): number;
|
|
178
|
+
pendingOffers: readonly {
|
|
179
|
+
id: string;
|
|
180
|
+
label: string;
|
|
181
|
+
}[] | null;
|
|
182
|
+
bolts: readonly BoltFx[];
|
|
183
|
+
pulses: readonly PulseFx[];
|
|
184
|
+
subscribe(listener: () => void): () => void;
|
|
185
|
+
}
|
|
186
|
+
export declare const WASD_KEYBINDS: ActionCodesMap;
|
|
187
|
+
export declare const XP_GEM_BASE_TYPE = "xp_gem";
|
|
188
|
+
export declare const HIT_EFFECT = "hit";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function leveled(value, level) {
|
|
2
|
+
if (typeof value === "number")
|
|
3
|
+
return value;
|
|
4
|
+
if ("table" in value) {
|
|
5
|
+
const index = Math.min(value.table.length - 1, Math.max(0, level - 1));
|
|
6
|
+
return value.table[index] ?? 0;
|
|
7
|
+
}
|
|
8
|
+
const raw = value.base + value.perLevel * (level - 1);
|
|
9
|
+
const floored = value.min === undefined ? raw : Math.max(value.min, raw);
|
|
10
|
+
return value.max === undefined ? floored : Math.min(value.max, floored);
|
|
11
|
+
}
|
|
12
|
+
export const WASD_KEYBINDS = {
|
|
13
|
+
moveForward: ["KeyW"],
|
|
14
|
+
moveBack: ["KeyS"],
|
|
15
|
+
moveLeft: ["KeyA"],
|
|
16
|
+
moveRight: ["KeyD"],
|
|
17
|
+
};
|
|
18
|
+
export const XP_GEM_BASE_TYPE = "xp_gem";
|
|
19
|
+
export const HIT_EFFECT = "hit";
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { leveled } from "./spec.js";
|
|
2
|
+
export function validateCartridge(spec) {
|
|
3
|
+
const problems = [];
|
|
4
|
+
const enemyIds = new Set(Object.keys(spec.enemies));
|
|
5
|
+
const weaponIds = new Set(Object.keys(spec.weapons));
|
|
6
|
+
if (spec.player.health <= 0)
|
|
7
|
+
problems.push(`player "${spec.player.kind}": health must be positive`);
|
|
8
|
+
if (spec.player.walkSpeed <= 0)
|
|
9
|
+
problems.push(`player "${spec.player.kind}": walkSpeed must be positive`);
|
|
10
|
+
if (enemyIds.has(spec.player.kind))
|
|
11
|
+
problems.push(`player kind "${spec.player.kind}" collides with an enemy id`);
|
|
12
|
+
if (spec.flow?.countdownSeconds !== undefined && spec.flow.countdownSeconds < 0) {
|
|
13
|
+
problems.push("flow.countdownSeconds must not be negative");
|
|
14
|
+
}
|
|
15
|
+
for (const [id, enemy] of Object.entries(spec.enemies)) {
|
|
16
|
+
if (enemy.health <= 0)
|
|
17
|
+
problems.push(`enemy "${id}": health must be positive`);
|
|
18
|
+
if (enemy.walkSpeed <= 0)
|
|
19
|
+
problems.push(`enemy "${id}": walkSpeed must be positive`);
|
|
20
|
+
if (enemy.xp <= 0)
|
|
21
|
+
problems.push(`enemy "${id}": xp must be positive`);
|
|
22
|
+
if (enemy.contact.damage <= 0)
|
|
23
|
+
problems.push(`enemy "${id}": contact.damage must be positive`);
|
|
24
|
+
if (enemy.contact.intervalSeconds <= 0)
|
|
25
|
+
problems.push(`enemy "${id}": contact.intervalSeconds must be positive`);
|
|
26
|
+
}
|
|
27
|
+
for (const wave of spec.spawning.director.waves) {
|
|
28
|
+
for (const entry of wave.entries) {
|
|
29
|
+
if (!enemyIds.has(entry.id))
|
|
30
|
+
problems.push(`spawning wave references unknown enemy "${entry.id}"`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
for (const [id, weapon] of Object.entries(spec.weapons)) {
|
|
34
|
+
for (let level = 1; level <= weapon.maxLevel; level += 1) {
|
|
35
|
+
if (leveled(weapon.damage, level) <= 0)
|
|
36
|
+
problems.push(`weapon "${id}": damage at level ${level} must be positive`);
|
|
37
|
+
if (leveled(weapon.cooldownMs, level) <= 0)
|
|
38
|
+
problems.push(`weapon "${id}": cooldownMs at level ${level} must be positive`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const seenUpgrades = new Set();
|
|
42
|
+
for (const upgrade of spec.progression.draft.upgrades) {
|
|
43
|
+
if (seenUpgrades.has(upgrade.id))
|
|
44
|
+
problems.push(`upgrade "${upgrade.id}": duplicate id`);
|
|
45
|
+
seenUpgrades.add(upgrade.id);
|
|
46
|
+
if (upgrade.maxStacks <= 0)
|
|
47
|
+
problems.push(`upgrade "${upgrade.id}": maxStacks must be positive`);
|
|
48
|
+
const effect = upgrade.effect;
|
|
49
|
+
if (effect.kind === "weaponLevel") {
|
|
50
|
+
if (!weaponIds.has(effect.weapon)) {
|
|
51
|
+
problems.push(`upgrade "${upgrade.id}": references unknown weapon "${effect.weapon}"`);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
const weapon = spec.weapons[effect.weapon];
|
|
55
|
+
if (1 + upgrade.maxStacks > weapon.maxLevel) {
|
|
56
|
+
problems.push(`upgrade "${upgrade.id}": maxStacks ${upgrade.maxStacks} exceeds weapon "${effect.weapon}" maxLevel ${weapon.maxLevel}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if ((effect.kind === "fieldAdd" || effect.kind === "fieldMultiply") && spec.fields?.[effect.field] === undefined) {
|
|
61
|
+
problems.push(`upgrade "${upgrade.id}": references undeclared field "${effect.field}"`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (spec.progression.draft.choices <= 0)
|
|
65
|
+
problems.push("progression.draft.choices must be positive");
|
|
66
|
+
if (spec.progression.maxLevel <= 1)
|
|
67
|
+
problems.push("progression.maxLevel must exceed 1");
|
|
68
|
+
const thresholds = spec.xpGems.rarityThresholds;
|
|
69
|
+
for (let i = 1; i < thresholds.length; i += 1) {
|
|
70
|
+
if (thresholds[i][0] >= thresholds[i - 1][0]) {
|
|
71
|
+
problems.push("xpGems.rarityThresholds must be sorted by descending value");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (spec.rules.win?.kind === "survive" && spec.rules.win.seconds <= 0) {
|
|
75
|
+
problems.push("rules.win.seconds must be positive");
|
|
76
|
+
}
|
|
77
|
+
return problems;
|
|
78
|
+
}
|
package/dist/cartridge.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ComponentType } from "react";
|
|
2
|
-
import { type CartridgeSpec } from "@jgengine/core/cartridge/spec";
|
|
3
2
|
import type { AbilitySlotState } from "@jgengine/core/combat/abilityKit";
|
|
4
3
|
import type { HudAnchor } from "@jgengine/core/ui/hudLayout";
|
|
4
|
+
import { type CartridgeSpec } from "./cartridge/spec.js";
|
|
5
5
|
import { type GameConfig } from "./defineGame.js";
|
|
6
6
|
import type { PlayableGame } from "./registry.js";
|
|
7
7
|
export type CartridgePanelItem = {
|
package/dist/cartridge.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
3
3
|
import { useFrame } from "@react-three/fiber";
|
|
4
|
-
import { createCartridge } from "@jgengine/core/cartridge/runtime";
|
|
5
|
-
import { WASD_KEYBINDS, leveled } from "@jgengine/core/cartridge/spec";
|
|
6
|
-
import { validateCartridge } from "@jgengine/core/cartridge/validate";
|
|
7
4
|
import { HudCanvas, HudPanel, useHudLayout } from "@jgengine/react";
|
|
8
5
|
import { useAbilitySlots, useEntityStat } from "@jgengine/react/hooks";
|
|
9
6
|
import { useGameContext } from "@jgengine/react/provider";
|
|
7
|
+
import { createCartridge } from "./cartridge/runtime.js";
|
|
8
|
+
import { WASD_KEYBINDS, leveled } from "./cartridge/spec.js";
|
|
9
|
+
import { validateCartridge } from "./cartridge/validate.js";
|
|
10
10
|
import { defineGame } from "./defineGame.js";
|
|
11
11
|
function useTick(intervalMs) {
|
|
12
12
|
const [, setTick] = useState(0);
|
|
@@ -229,6 +229,7 @@ export function cartridge(config) {
|
|
|
229
229
|
return defineGame({
|
|
230
230
|
name: config.name,
|
|
231
231
|
assets: config.assets,
|
|
232
|
+
features: { leaderboard: config.rules.killLeaderboardStat !== undefined },
|
|
232
233
|
world: config.world,
|
|
233
234
|
physics: config.physics,
|
|
234
235
|
input: config.input ?? WASD_KEYBINDS,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { GameContext } from "@jgengine/core/runtime/gameContext";
|
|
2
|
+
import type { LiveGameBackend } from "@jgengine/core/runtime/transport";
|
|
3
|
+
/** Where a gameplay command goes when the shell dispatches it — run locally, or sent to the authoritative host. */
|
|
4
|
+
export interface CommandSink {
|
|
5
|
+
run(name: string, input: unknown): void;
|
|
6
|
+
}
|
|
7
|
+
/** Runs commands on the local `ctx` — the default, client-authoritative path.
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export declare function localCommandSink(ctx: GameContext): CommandSink;
|
|
11
|
+
/** Sends commands to the authoritative host over the transport; the result replicates back through world sync.
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export declare function remoteCommandSink(backend: Pick<LiveGameBackend, "transport">, serverId: string): CommandSink;
|
|
15
|
+
/**
|
|
16
|
+
* The sink a server-authoritative shell should dispatch gameplay commands through: remote when the game opts into
|
|
17
|
+
* `authority: "server"` and a server is joined, local otherwise. Client-only UI commands (targeting, hotbar
|
|
18
|
+
* scroll) keep calling `ctx.game.commands.run` directly — only authoritative gameplay verbs route to the host.
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveCommandSink(ctx: GameContext, opts: {
|
|
22
|
+
serverAuthoritative: boolean;
|
|
23
|
+
backend: Pick<LiveGameBackend, "transport"> | null;
|
|
24
|
+
serverId: string | null;
|
|
25
|
+
}): CommandSink;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Runs commands on the local `ctx` — the default, client-authoritative path.
|
|
2
|
+
* @internal
|
|
3
|
+
*/
|
|
4
|
+
export function localCommandSink(ctx) {
|
|
5
|
+
return {
|
|
6
|
+
run(name, input) {
|
|
7
|
+
ctx.game.commands.run(name, input);
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/** Sends commands to the authoritative host over the transport; the result replicates back through world sync.
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export function remoteCommandSink(backend, serverId) {
|
|
15
|
+
return {
|
|
16
|
+
run(name, input) {
|
|
17
|
+
void backend.transport.runCommand({ serverId, command: name, input }).catch(() => undefined);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The sink a server-authoritative shell should dispatch gameplay commands through: remote when the game opts into
|
|
23
|
+
* `authority: "server"` and a server is joined, local otherwise. Client-only UI commands (targeting, hotbar
|
|
24
|
+
* scroll) keep calling `ctx.game.commands.run` directly — only authoritative gameplay verbs route to the host.
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export function resolveCommandSink(ctx, opts) {
|
|
28
|
+
if (opts.serverAuthoritative && opts.backend !== null && opts.serverId !== null) {
|
|
29
|
+
return remoteCommandSink(opts.backend, opts.serverId);
|
|
30
|
+
}
|
|
31
|
+
return localCommandSink(ctx);
|
|
32
|
+
}
|
package/dist/defineGame.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { defineGame as defineEngineGame, } from "@jgengine/core/game/defineGame";
|
|
3
|
+
import { syncLifecyclePhase } from "@jgengine/core/game/gamePhase";
|
|
3
4
|
import { offline } from "@jgengine/core/runtime/adapter";
|
|
4
5
|
import { EnvironmentScene } from "./environment.js";
|
|
5
6
|
const noop = () => { };
|
|
@@ -10,21 +11,36 @@ function worldBackdrop(feature) {
|
|
|
10
11
|
}
|
|
11
12
|
const emptyUi = () => null;
|
|
12
13
|
export function defineGame(config) {
|
|
13
|
-
const { content, loop, GameUI, environment, camera, multiplayer, WorldOverlay, renderEntity, renderObject, entitySprites, entityModels, objectModels, hotbarSelection, prompts, pointer, touch, orientation, worldHealthBars, audio, entitySounds, objectSounds, worldItem, collision, movement, lighting, backdrop, shadows, presentation, objectStyles, devtools, ...engineFields } = config;
|
|
14
|
-
const game = defineEngineGame({
|
|
14
|
+
const { content, loop, GameUI, environment, camera, multiplayer, WorldOverlay, viewmodel, renderEntity, renderObject, entitySprites, entityModels, objectModels, hotbarSelection, prompts, pointer, touch, orientation, worldHealthBars, nameplates, audio, entitySounds, objectSounds, worldItem, collision, movement, lighting, backdrop, postProcessing, shadows, presentation, objectStyles, devtools, capture, platforms, hudFit, ...engineFields } = config;
|
|
15
|
+
const game = defineEngineGame({
|
|
16
|
+
...engineFields,
|
|
17
|
+
multiplayer: multiplayer ?? offline(),
|
|
18
|
+
loop,
|
|
19
|
+
});
|
|
20
|
+
function withPhaseSync(inner) {
|
|
21
|
+
return (ctx, ...args) => {
|
|
22
|
+
inner?.(ctx, ...args);
|
|
23
|
+
syncLifecyclePhase(ctx, game.lifecycle);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const composed = game.loop;
|
|
15
27
|
return {
|
|
16
28
|
game,
|
|
17
29
|
content: content ?? {},
|
|
18
30
|
loop: {
|
|
19
|
-
onInit:
|
|
20
|
-
onNewPlayer:
|
|
21
|
-
onTick:
|
|
31
|
+
onInit: withPhaseSync(composed?.onInit),
|
|
32
|
+
onNewPlayer: withPhaseSync(composed?.onNewPlayer),
|
|
33
|
+
onTick: withPhaseSync(composed?.onTick),
|
|
34
|
+
onPlayerLeave: composed?.onPlayerLeave ?? noop,
|
|
35
|
+
onReset: composed?.onReset?.bind(composed) ?? noop,
|
|
36
|
+
onDispose: composed?.onDispose?.bind(composed) ?? noop,
|
|
22
37
|
},
|
|
23
38
|
GameUI: GameUI ?? emptyUi,
|
|
24
39
|
environment: environment ??
|
|
25
40
|
(game.world?.kind === "environment" ? worldBackdrop(game.world) : undefined),
|
|
26
41
|
camera: camera ?? { perspective: "third" },
|
|
27
42
|
WorldOverlay,
|
|
43
|
+
viewmodel,
|
|
28
44
|
renderEntity,
|
|
29
45
|
renderObject,
|
|
30
46
|
entitySprites,
|
|
@@ -36,6 +52,7 @@ export function defineGame(config) {
|
|
|
36
52
|
touch,
|
|
37
53
|
orientation,
|
|
38
54
|
worldHealthBars,
|
|
55
|
+
nameplates,
|
|
39
56
|
audio,
|
|
40
57
|
entitySounds,
|
|
41
58
|
objectSounds,
|
|
@@ -44,9 +61,13 @@ export function defineGame(config) {
|
|
|
44
61
|
movement,
|
|
45
62
|
lighting,
|
|
46
63
|
backdrop,
|
|
64
|
+
postProcessing,
|
|
47
65
|
shadows,
|
|
48
66
|
presentation,
|
|
49
67
|
objectStyles,
|
|
50
68
|
devtools,
|
|
69
|
+
capture,
|
|
70
|
+
platforms,
|
|
71
|
+
hudFit,
|
|
51
72
|
};
|
|
52
73
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ColPanel(): import("react").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useSyncExternalStore } from "react";
|
|
3
|
+
import { COLLISION_DEBUG_LAYERS, collisionDebug } from "./collisionDebug.js";
|
|
4
|
+
const LAYER_LABELS = {
|
|
5
|
+
hitboxes: "Damage hitboxes",
|
|
6
|
+
bodies: "Physical bodies",
|
|
7
|
+
projectiles: "Projectile paths",
|
|
8
|
+
muzzles: "Muzzle / shot origins",
|
|
9
|
+
aimLaser: "Aim laser (authoritative)",
|
|
10
|
+
};
|
|
11
|
+
export function ColPanel() {
|
|
12
|
+
const state = useSyncExternalStore(collisionDebug.subscribe, () => collisionDebug.getState(), () => collisionDebug.getState());
|
|
13
|
+
return (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "text-[10px] text-neutral-500", children: "F2+D world collision debugger \u00B7 layers stay on when panel closes \u00B7 zero cost when all off" }), _jsxs("div", { className: "flex gap-1", children: [_jsx("button", { type: "button", className: "rounded-md bg-white/[0.04] px-2 py-0.5 text-neutral-300 ring-1 ring-inset ring-white/[0.08] transition-colors hover:bg-white/10", onClick: () => collisionDebug.setAllLayers(true), children: "All on" }), _jsx("button", { type: "button", className: "rounded-md bg-white/[0.04] px-2 py-0.5 text-neutral-300 ring-1 ring-inset ring-white/[0.08] transition-colors hover:bg-white/10", onClick: () => collisionDebug.setAllLayers(false), children: "All off" })] }), _jsx("div", { className: "space-y-1", children: COLLISION_DEBUG_LAYERS.map((layer) => (_jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-neutral-200", children: [_jsx("input", { type: "checkbox", checked: state.layers[layer], onChange: (event) => collisionDebug.setLayer(layer, event.target.checked) }), _jsx("span", { children: LAYER_LABELS[layer] })] }, layer))) }), _jsx("div", { className: "border-t border-neutral-800 pt-1.5 font-mono text-[9px] text-neutral-500", children: "hitbox pink \u00B7 body cyan \u00B7 muzzle red \u00B7 laser lime \u00B7 X damage \u00B7 \u25CB solid \u00B7 \u00B7 miss" })] }));
|
|
14
|
+
}
|
|
@@ -136,6 +136,7 @@ export function CollisionDebugWorld() {
|
|
|
136
136
|
},
|
|
137
137
|
positionOf: (id) => ctx.scene.entity.get(id)?.position,
|
|
138
138
|
rotationYOf: (id) => ctx.scene.entity.get(id)?.rotationY,
|
|
139
|
+
collidersOf: (id) => ctx.scene.entity.collidersOf(id),
|
|
139
140
|
from: probe.from,
|
|
140
141
|
aim: probe.aim,
|
|
141
142
|
originPolicy: probe.originPolicy,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type DevtoolsSnapshot } from "@jgengine/core/devtools/devtools";
|
|
2
2
|
import type { GameContext } from "@jgengine/core/runtime/gameContext";
|
|
3
3
|
import type { ShellMultiplayer } from "../multiplayer.js";
|
|
4
4
|
import type { PlayableGame } from "../registry.js";
|
|
5
|
-
export
|
|
6
|
-
|
|
5
|
+
export { applyStoredDevtoolsOverrides, persistDevtoolsOverrides } from "./devtoolsOverrides.js";
|
|
6
|
+
/** @internal */
|
|
7
7
|
export declare function withDevtoolsLatency(multiplayer: ShellMultiplayer): ShellMultiplayer;
|
|
8
|
+
/** @internal */
|
|
8
9
|
export declare function DevtoolsRendererProbe(): null;
|
|
10
|
+
/** @internal */
|
|
9
11
|
export declare function buildLeanReport(playable: PlayableGame): {
|
|
10
12
|
game: string;
|
|
11
13
|
at: number;
|
|
@@ -67,9 +69,11 @@ export declare function buildLeanReport(playable: PlayableGame): {
|
|
|
67
69
|
}[] | undefined;
|
|
68
70
|
discoveredCount: number;
|
|
69
71
|
};
|
|
72
|
+
/** @internal */
|
|
70
73
|
export declare function buildFullReport(playable: PlayableGame): DevtoolsSnapshot & {
|
|
71
74
|
game: string;
|
|
72
75
|
};
|
|
76
|
+
/** @internal */
|
|
73
77
|
export declare function DevtoolsOverlay({ open, ctx, playable, multiplayer, }: {
|
|
74
78
|
open: boolean;
|
|
75
79
|
ctx: GameContext;
|