@jgengine/shell 0.8.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.
- package/CHANGELOG.md +16 -0
- package/README.md +11 -0
- package/dist/GamePlayerShell.d.ts +5 -2
- package/dist/GamePlayerShell.js +358 -98
- package/dist/GameUiPreview.js +2 -1
- package/dist/audio/AudioComponents.js +25 -8
- package/dist/audio/audioEngine.d.ts +1 -0
- package/dist/audio/audioEngine.js +4 -0
- package/dist/behaviour.d.ts +5 -24
- package/dist/behaviour.js +14 -50
- package/dist/behaviourAttach.d.ts +9 -0
- package/dist/behaviourAttach.js +32 -0
- package/dist/behaviourDriver.d.ts +7 -0
- package/dist/behaviourDriver.js +21 -0
- package/dist/camera/GameCameraRig.js +1 -1
- package/dist/camera/GameFirstPersonCamera.js +14 -0
- package/dist/camera/GameOrbitCamera.js +10 -1
- package/dist/camera/PlayerFov.d.ts +18 -0
- package/dist/camera/PlayerFov.js +48 -0
- package/dist/camera/cameraBlendMath.d.ts +13 -0
- package/dist/camera/cameraBlendMath.js +36 -0
- package/dist/camera/cameraRigs.d.ts +3 -0
- package/dist/camera/cameraRigs.js +36 -33
- package/dist/camera/fovPreference.d.ts +25 -0
- package/dist/camera/fovPreference.js +73 -0
- package/dist/camera/index.d.ts +3 -0
- package/dist/camera/index.js +3 -0
- package/dist/camera/rigMath.d.ts +10 -0
- package/dist/camera/rigMath.js +36 -1
- package/dist/camera/shakeChannel.d.ts +3 -17
- package/dist/camera/shakeChannel.js +2 -21
- package/dist/camera/shakeChannelMath.d.ts +8 -0
- package/dist/camera/shakeChannelMath.js +21 -0
- package/dist/cartridge.d.ts +145 -0
- package/dist/cartridge.js +245 -0
- package/dist/defineGame.js +2 -1
- package/dist/devtools/CollisionDebugWorld.d.ts +5 -0
- package/dist/devtools/CollisionDebugWorld.js +180 -0
- package/dist/devtools/DevtoolsOverlay.d.ts +65 -1
- package/dist/devtools/DevtoolsOverlay.js +383 -41
- package/dist/devtools/collisionDebug.d.ts +57 -0
- package/dist/devtools/collisionDebug.js +127 -0
- package/dist/devtools/collisionDebugMath.d.ts +102 -0
- package/dist/devtools/collisionDebugMath.js +128 -0
- package/dist/environment/Daylight.d.ts +13 -7
- package/dist/environment/Daylight.js +12 -11
- package/dist/environment/EnvironmentScene.js +82 -35
- package/dist/environment/groundPadMath.d.ts +2 -2
- package/dist/environment/groundPadMath.js +1 -1
- package/dist/environment/index.d.ts +2 -1
- package/dist/environment/index.js +1 -0
- package/dist/environment/skyLightingPolicy.d.ts +10 -0
- package/dist/environment/skyLightingPolicy.js +6 -0
- package/dist/input/mouseLook.d.ts +27 -0
- package/dist/input/mouseLook.js +35 -0
- package/dist/materialOverride.d.ts +4 -6
- package/dist/materialOverride.js +12 -16
- package/dist/pointer/PointerProbe.js +6 -2
- package/dist/pointer/pointerService.d.ts +3 -0
- package/dist/pointer/pointerService.js +6 -0
- package/dist/render/modelRender.d.ts +10 -1
- package/dist/render/modelRender.js +32 -5
- package/dist/render/resolveModel.d.ts +14 -0
- package/dist/render/resolveModel.js +24 -0
- package/dist/settings/QuickControls.d.ts +4 -0
- package/dist/settings/QuickControls.js +42 -0
- package/dist/settings/SettingsChrome.d.ts +1 -0
- package/dist/settings/SettingsChrome.js +10 -0
- package/dist/settings/SettingsMenu.d.ts +6 -0
- package/dist/settings/SettingsMenu.js +148 -0
- package/dist/settings/SettingsRuntime.d.ts +11 -0
- package/dist/settings/SettingsRuntime.js +19 -0
- package/dist/settings/appliedSettings.d.ts +14 -0
- package/dist/settings/appliedSettings.js +27 -0
- package/dist/settings/settingsController.d.ts +20 -0
- package/dist/settings/settingsController.js +165 -0
- package/dist/structures/GeneratedBuilding.d.ts +10 -0
- package/dist/structures/GeneratedBuilding.js +96 -8
- package/dist/structures/index.d.ts +1 -1
- package/dist/structures/index.js +1 -1
- package/dist/terrain/CarvedTerrain.d.ts +2 -1
- package/dist/terrain/CarvedTerrain.js +3 -3
- package/dist/terrain/GrassField.d.ts +3 -1
- package/dist/terrain/GrassField.js +4 -2
- package/dist/terrain/grassBudget.d.ts +3 -0
- package/dist/terrain/grassBudget.js +6 -0
- package/dist/terrain/grassGeometry.js +1 -1
- package/dist/terrain/terrainMath.d.ts +8 -0
- package/dist/terrain/terrainMath.js +9 -0
- package/dist/touch/OrientationHint.d.ts +3 -0
- package/dist/touch/OrientationHint.js +13 -0
- package/dist/touch/TouchControlsOverlay.d.ts +17 -1
- package/dist/touch/TouchControlsOverlay.js +115 -9
- package/dist/visibility/CullingProvider.d.ts +21 -0
- package/dist/visibility/CullingProvider.js +134 -0
- package/dist/vision/FrustumSensorHud.d.ts +1 -6
- package/dist/vision/FrustumSensorHud.js +25 -27
- package/dist/vision/frustumSampleEqual.d.ts +2 -0
- package/dist/vision/frustumSampleEqual.js +10 -0
- package/dist/water/Ocean.js +1 -1
- package/dist/water/OceanConfig.d.ts +13 -0
- package/dist/water/OceanConfig.js +25 -17
- package/dist/water/index.d.ts +1 -1
- package/dist/water/index.js +1 -1
- package/dist/weather/FireSpreadLayer.js +7 -2
- package/dist/weather/RainField.d.ts +3 -1
- package/dist/weather/RainField.js +4 -4
- package/dist/weather/SnowField.d.ts +3 -1
- package/dist/weather/SnowField.js +4 -4
- package/dist/weather/fireSpreadPose.d.ts +2 -0
- package/dist/weather/fireSpreadPose.js +4 -0
- package/dist/weather/weatherMath.d.ts +5 -1
- package/dist/weather/weatherMath.js +7 -2
- package/dist/world/DataObjects.d.ts +1 -1
- package/dist/world/DataObjects.js +3 -1
- package/dist/world/SpriteBatch.d.ts +44 -0
- package/dist/world/SpriteBatch.js +112 -0
- package/dist/world/WorldHud.d.ts +3 -0
- package/dist/world/WorldHud.js +89 -42
- package/dist/world/entityPose.d.ts +14 -0
- package/dist/world/entityPose.js +10 -0
- package/dist/world/telegraphPulse.d.ts +1 -0
- package/dist/world/telegraphPulse.js +4 -0
- package/dist/world/worldBarSamples.d.ts +30 -0
- package/dist/world/worldBarSamples.js +51 -0
- package/llms.txt +1404 -1143
- package/package.json +4 -4
package/dist/GamePlayerShell.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Canvas, useFrame, useLoader } from "@react-three/fiber";
|
|
3
|
-
import { Component, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from "react";
|
|
3
|
+
import { Component, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from "react";
|
|
4
4
|
import * as THREE from "three";
|
|
5
5
|
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
6
6
|
import { actionRepeatMs, createActionStateTracker, hotbarSlotActionIndex, resolveActionCommand, shouldDispatchAction, toActionStateBindingMap, } from "@jgengine/core/input/actionBindings";
|
|
@@ -8,35 +8,55 @@ import { deriveTouchScheme, withTouchCodes } from "@jgengine/core/input/touchSch
|
|
|
8
8
|
import { buildContextMenu, contextVerbInput, } from "@jgengine/core/interaction/contextMenu";
|
|
9
9
|
import { resolveActivePrompt } from "@jgengine/core/interaction/proximityPrompt";
|
|
10
10
|
import { aimToPoint } from "@jgengine/core/input/pointer";
|
|
11
|
+
import { normalizePointerToAxis } from "@jgengine/core/input/pointerAxis";
|
|
11
12
|
import { createSelectionSet, isMarquee, screenRect, selectWithinRect, } from "@jgengine/core/scene/selection";
|
|
12
13
|
import { advancePlayerMotion, constrainStepToAxis, createEmptyMovementKeys, createPlayerMotionState, resolveMovementIntent, resolveObstacleStep, snapPositionToGrid, } from "@jgengine/core/movement/movementModel";
|
|
14
|
+
import { steerYaw } from "@jgengine/core/movement/steering";
|
|
13
15
|
import { advanceVoxelPlayer, createVoxelPlayerBody, } from "@jgengine/core/movement/voxelController";
|
|
14
16
|
import { createGameContext } from "@jgengine/core/runtime/gameContext";
|
|
17
|
+
import { applyHorizontalImpulses } from "@jgengine/core/runtime/motionIntents";
|
|
15
18
|
import { groundFieldFor } from "@jgengine/core/world/terrain";
|
|
16
19
|
import { objectVisualScale } from "@jgengine/core/scene/objectStore";
|
|
17
20
|
import { DEFAULT_PICKUP_RADIUS, WORLD_ITEM_ENTITY_NAME } from "@jgengine/core/game/worldItem";
|
|
18
21
|
import { useGameContext } from "@jgengine/react/provider";
|
|
19
22
|
import { useDisplayProfile } from "@jgengine/react/display";
|
|
23
|
+
import { HudViewportProvider } from "@jgengine/react/hudViewport";
|
|
20
24
|
import { useSceneEntities, useSceneObjects, usePlayer, useTarget } from "@jgengine/react/hooks";
|
|
21
25
|
import { GameProvider } from "@jgengine/react/provider";
|
|
22
26
|
import { CAMERA_FRUSTUM_DEFAULTS } from "@jgengine/core/game/playableGame";
|
|
27
|
+
import { createSettingsStore } from "@jgengine/core/settings/settingsModel";
|
|
28
|
+
import { applyBindingOverrides, clearBindingOverride, loadBindingOverrides, saveBindingOverride, } from "@jgengine/core/input/bindingOverrides";
|
|
29
|
+
import { playControlsActive } from "@jgengine/core/game/controlGate";
|
|
23
30
|
import { sky as resolveSkyDescriptor } from "@jgengine/core/world/features";
|
|
24
31
|
import { devtools } from "@jgengine/core/devtools/devtools";
|
|
32
|
+
import { VERSION } from "@jgengine/core/meta/changelog";
|
|
25
33
|
import { AudioListener, EntityAudioEmitters, ObjectAudioEmitters } from "./audio/AudioComponents.js";
|
|
26
34
|
import { createAudioEngine } from "./audio/audioEngine.js";
|
|
35
|
+
import { CollisionDebugWorld } from "./devtools/CollisionDebugWorld.js";
|
|
36
|
+
import { collisionDebug } from "./devtools/collisionDebug.js";
|
|
27
37
|
import { DevtoolsOverlay, DevtoolsRendererProbe, withDevtoolsLatency } from "./devtools/DevtoolsOverlay.js";
|
|
28
|
-
import { GAME_SIM_FRAME_PRIORITY, GameCameraRig, resolveRigKind, rtsPanKeysConflict } from "./camera/index.js";
|
|
38
|
+
import { GAME_SIM_FRAME_PRIORITY, GameCameraRig, PlayerFovProvider, PlayerFovSlider, resolveRigKind, rtsPanKeysConflict, } from "./camera/index.js";
|
|
39
|
+
import { resolveModel, tryResolveCatalogModel } from "./render/resolveModel.js";
|
|
40
|
+
import { CullingProvider, useRenderVisibility } from "./visibility/CullingProvider.js";
|
|
29
41
|
import { SkyDaylight, TimeOfDayDaylight } from "./environment.js";
|
|
42
|
+
import { resolveSkyLightOwnership, skyEmitsLights } from "./environment/skyLightingPolicy.js";
|
|
30
43
|
import { EnvironmentScene } from "./environment/EnvironmentScene.js";
|
|
31
44
|
import { applyMaterialOverride } from "./materialOverride.js";
|
|
32
45
|
import { PointerProbe } from "./pointer/PointerProbe.js";
|
|
33
|
-
import {
|
|
46
|
+
import { applyPaintTextureToMaterials, cacheStandardMaterials, cloneModelScene, createPaintCanvas, disposeClonedMaterials, syncPaintCanvas, } from "./render/modelRender.js";
|
|
47
|
+
import { writeEntityPose } from "./world/entityPose.js";
|
|
34
48
|
import { MarqueeBox, ContextMenuView } from "./pointer/PointerOverlays.js";
|
|
35
49
|
import { createPointerService, POINTER_ENTITY_KEY, POINTER_OBJECT_KEY, } from "./pointer/pointerService.js";
|
|
36
50
|
import { CombatCameraShake, ProjectileTracers, Reticle, WorldEntityBars, WorldFloatText, WorldTelegraphs, } from "./world/WorldHud.js";
|
|
37
51
|
import { GridWorldScene } from "./world/GridWorldScene.js";
|
|
38
52
|
import { WorldItems } from "./world/WorldItems.js";
|
|
39
|
-
import {
|
|
53
|
+
import { OrientationHint } from "./touch/OrientationHint.js";
|
|
54
|
+
import { TouchControlsDock, TouchPlaySurface, touchDockClearance } from "./touch/TouchControlsOverlay.js";
|
|
55
|
+
import { BUILT_IN_SETTING_CATEGORIES } from "@jgengine/core/settings/settingsModel";
|
|
56
|
+
import { SettingsProvider } from "@jgengine/react/settings";
|
|
57
|
+
import { SettingsRuntime } from "./settings/SettingsRuntime.js";
|
|
58
|
+
import { SettingsChrome } from "./settings/SettingsChrome.js";
|
|
59
|
+
import { AudioSettingsBridge, useGraphicsSettings } from "./settings/appliedSettings.js";
|
|
40
60
|
const DEV_USER_ID = "dev-player";
|
|
41
61
|
const TURN_SPEED = 2.4;
|
|
42
62
|
const PRIMARY_CLICK_MOVE_THRESHOLD_PX = 6;
|
|
@@ -44,14 +64,15 @@ const GROUND_SIZE = 160;
|
|
|
44
64
|
const GROUND_SEGMENTS = 80;
|
|
45
65
|
const DEFAULT_BACKGROUND_COLOR = "#14161b";
|
|
46
66
|
const DEFAULT_BACKDROP_FOG_COLOR = "#1a1c22";
|
|
47
|
-
function errorToDiagnostic(error, phase) {
|
|
67
|
+
function errorToDiagnostic(error, phase, componentStack) {
|
|
68
|
+
const capturedAt = new Date().toISOString();
|
|
48
69
|
if (error instanceof Error) {
|
|
49
|
-
return { phase, message: error.message, stack: error.stack };
|
|
70
|
+
return { phase, message: error.message, stack: error.stack, componentStack, capturedAt };
|
|
50
71
|
}
|
|
51
|
-
return { phase, message: typeof error === "string" ? error : JSON.stringify(error) };
|
|
72
|
+
return { phase, message: typeof error === "string" ? error : JSON.stringify(error), componentStack, capturedAt };
|
|
52
73
|
}
|
|
53
|
-
function logRuntimeError(error, phase) {
|
|
54
|
-
const diagnostic = errorToDiagnostic(error, phase);
|
|
74
|
+
function logRuntimeError(error, phase, componentStack) {
|
|
75
|
+
const diagnostic = errorToDiagnostic(error, phase, componentStack);
|
|
55
76
|
console.error(`[jgengine:${phase}] ${diagnostic.message}`, error);
|
|
56
77
|
return diagnostic;
|
|
57
78
|
}
|
|
@@ -173,18 +194,20 @@ export function resolveWorldSky(world) {
|
|
|
173
194
|
* flipped the sign and launched airborne players upward instead.
|
|
174
195
|
*/
|
|
175
196
|
export function resolvePhysicsTuning(physics) {
|
|
176
|
-
if (physics
|
|
197
|
+
if (physics === undefined)
|
|
177
198
|
return undefined;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
199
|
+
return {
|
|
200
|
+
get gravityAcceleration() {
|
|
201
|
+
return physics.gravity === undefined ? undefined : -physics.gravity;
|
|
202
|
+
},
|
|
203
|
+
get jumpVelocity() {
|
|
204
|
+
return physics.jumpVelocity;
|
|
205
|
+
},
|
|
206
|
+
};
|
|
184
207
|
}
|
|
185
208
|
/** True when the world is an environment feature with terrain, so the voxel controller should sample its height. */
|
|
186
209
|
export function hasEnvironmentTerrain(world) {
|
|
187
|
-
return world?.kind === "environment" && world.terrain !== undefined;
|
|
210
|
+
return world?.kind === "environment" && (world.terrain !== undefined || (world.islands?.length ?? 0) > 0);
|
|
188
211
|
}
|
|
189
212
|
function colorFromId(id) {
|
|
190
213
|
let hash = 0;
|
|
@@ -227,9 +250,12 @@ function EntityModel({ model, instanceId }) {
|
|
|
227
250
|
const scene = useMemo(() => {
|
|
228
251
|
const cloned = cloneModelScene(gltf.scene);
|
|
229
252
|
if (material !== undefined)
|
|
230
|
-
applyMaterialOverride(cloned, material);
|
|
253
|
+
applyMaterialOverride(cloned, material, { clone: false });
|
|
231
254
|
return cloned;
|
|
232
255
|
}, [gltf, material]);
|
|
256
|
+
useEffect(() => () => {
|
|
257
|
+
disposeClonedMaterials(scene);
|
|
258
|
+
}, [scene]);
|
|
233
259
|
const animation = model.animation;
|
|
234
260
|
const mixerRef = useRef(null);
|
|
235
261
|
const animationPausedRef = useRef(false);
|
|
@@ -261,10 +287,12 @@ function EntityModel({ model, instanceId }) {
|
|
|
261
287
|
const paintCanvasRef = useRef(null);
|
|
262
288
|
const paintDrawnCountRef = useRef(0);
|
|
263
289
|
const paintVersionRef = useRef(-1);
|
|
290
|
+
const materialCacheRef = useRef(null);
|
|
264
291
|
useEffect(() => {
|
|
265
292
|
paintCanvasRef.current = null;
|
|
266
293
|
paintDrawnCountRef.current = 0;
|
|
267
294
|
paintVersionRef.current = -1;
|
|
295
|
+
materialCacheRef.current = null;
|
|
268
296
|
}, [scene]);
|
|
269
297
|
useFrame((_state, delta) => {
|
|
270
298
|
if (mixerRef.current !== null && !animationPausedRef.current)
|
|
@@ -277,45 +305,68 @@ function EntityModel({ model, instanceId }) {
|
|
|
277
305
|
return;
|
|
278
306
|
paintVersionRef.current = version;
|
|
279
307
|
const strokes = paint.strokes(instanceId);
|
|
308
|
+
const cache = cacheStandardMaterials(scene, materialCacheRef.current);
|
|
309
|
+
materialCacheRef.current = cache;
|
|
280
310
|
if (paintCanvasRef.current === null) {
|
|
281
311
|
if (strokes.length === 0)
|
|
282
312
|
return;
|
|
283
|
-
const
|
|
284
|
-
const seed = materials[0];
|
|
313
|
+
const seed = cache.materials[0];
|
|
285
314
|
if (seed === undefined)
|
|
286
315
|
return;
|
|
287
316
|
const paintCanvas = createPaintCanvas(seed);
|
|
288
317
|
paintCanvasRef.current = paintCanvas;
|
|
289
|
-
|
|
318
|
+
applyPaintTextureToMaterials(cache.materials, paintCanvas);
|
|
290
319
|
}
|
|
291
|
-
|
|
292
|
-
paintDrawnCountRef.current = syncPaintCanvas(paintCanvasRef.current, seedColor, strokes, paintDrawnCountRef.current);
|
|
320
|
+
paintDrawnCountRef.current = syncPaintCanvas(paintCanvasRef.current, cache.seedColor, strokes, paintDrawnCountRef.current);
|
|
293
321
|
});
|
|
294
322
|
return _jsx("primitive", { object: scene, position: position, scale: [scale, scale, scale] });
|
|
295
323
|
}
|
|
296
|
-
function resolveModel(value, assets) {
|
|
297
|
-
if (value === undefined)
|
|
298
|
-
return undefined;
|
|
299
|
-
if (typeof value !== "string")
|
|
300
|
-
return value;
|
|
301
|
-
const ref = assets.resolve(value);
|
|
302
|
-
if (ref === null)
|
|
303
|
-
return undefined;
|
|
304
|
-
return ref.dims === undefined ? { url: ref.url } : { url: ref.url, dims: ref.dims };
|
|
305
|
-
}
|
|
306
324
|
function EntityMarker({ entity, custom, model, sprite, isLocal, targeted, selected, onSelect, }) {
|
|
307
|
-
const
|
|
308
|
-
|
|
325
|
+
const groupRef = useRef(null);
|
|
326
|
+
const ctx = useGameContext();
|
|
327
|
+
const visibleRef = useRenderVisibility();
|
|
328
|
+
const entityId = entity.id;
|
|
329
|
+
const role = entity.role;
|
|
330
|
+
const name = entity.name;
|
|
331
|
+
const color = isLocal ? "#4ade80" : role === "npc" ? colorFromId(name) : "#9ca3af";
|
|
332
|
+
useFrame(() => {
|
|
333
|
+
const group = groupRef.current;
|
|
334
|
+
if (group === null)
|
|
335
|
+
return;
|
|
336
|
+
const live = ctx.scene.entity.get(entityId);
|
|
337
|
+
if (live === null)
|
|
338
|
+
return;
|
|
339
|
+
writeEntityPose(group, live);
|
|
340
|
+
group.visible = visibleRef.current(entityId);
|
|
341
|
+
});
|
|
342
|
+
return (_jsxs("group", { ref: groupRef, userData: { [POINTER_ENTITY_KEY]: entityId }, onPointerDown: (event) => {
|
|
309
343
|
event.stopPropagation();
|
|
310
|
-
if (
|
|
311
|
-
|
|
312
|
-
|
|
344
|
+
if (isLocal)
|
|
345
|
+
return;
|
|
346
|
+
const live = ctx.scene.entity.get(entityId);
|
|
347
|
+
if (live !== null)
|
|
348
|
+
onSelect(live);
|
|
349
|
+
}, children: [selected ? (_jsxs("mesh", { "rotation-x": -Math.PI / 2, "position-y": 0.02, children: [_jsx("ringGeometry", { args: [0.8, 0.95, 32] }), _jsx("meshBasicMaterial", { color: "#34d399", transparent: true, opacity: 0.9 })] })) : null, custom !== undefined && custom !== null ? (custom) : model !== undefined ? (_jsx(EntityModel, { model: model, instanceId: entityId })) : sprite !== undefined ? (_jsx(EntitySprite, { sprite: sprite })) : role === "prop" ? (_jsxs("mesh", { "position-y": 0.5, children: [_jsx("sphereGeometry", { args: [0.45, 16, 16] }), _jsx("meshStandardMaterial", { color: color })] })) : (_jsxs(_Fragment, { children: [_jsxs("mesh", { "position-y": 0.95, children: [_jsx("capsuleGeometry", { args: [0.35, 1.1, 6, 14] }), _jsx("meshStandardMaterial", { color: color })] }), _jsxs("mesh", { position: [0, 1.35, 0.32], children: [_jsx("boxGeometry", { args: [0.16, 0.16, 0.16] }), _jsx("meshStandardMaterial", { color: "#f8fafc" })] })] })), targeted ? (_jsxs("mesh", { "rotation-x": -Math.PI / 2, "position-y": 0.03, children: [_jsx("ringGeometry", { args: [0.6, 0.75, 28] }), _jsx("meshBasicMaterial", { color: "#f87171" })] })) : null] }));
|
|
313
350
|
}
|
|
314
351
|
function ObjectMarker({ object, custom, model, style, }) {
|
|
352
|
+
const groupRef = useRef(null);
|
|
353
|
+
const ctx = useGameContext();
|
|
354
|
+
const visibleRef = useRenderVisibility();
|
|
355
|
+
const instanceId = object.instanceId;
|
|
315
356
|
const [scaleX, scaleY, scaleZ] = objectVisualScale(object.visual);
|
|
316
357
|
const color = object.visual?.color ?? style?.color ?? colorFromId(object.catalogId);
|
|
317
358
|
const opacity = object.visual?.opacity ?? style?.opacity ?? 1;
|
|
318
|
-
|
|
359
|
+
useFrame(() => {
|
|
360
|
+
const group = groupRef.current;
|
|
361
|
+
if (group === null)
|
|
362
|
+
return;
|
|
363
|
+
const live = ctx.scene.object.get(instanceId);
|
|
364
|
+
if (live === null)
|
|
365
|
+
return;
|
|
366
|
+
writeEntityPose(group, live);
|
|
367
|
+
group.visible = visibleRef.current(instanceId);
|
|
368
|
+
});
|
|
369
|
+
return (_jsx("group", { ref: groupRef, userData: { [POINTER_OBJECT_KEY]: instanceId }, children: custom !== undefined && custom !== null ? (custom) : model !== undefined ? (_jsx(EntityModel, { model: model, instanceId: instanceId })) : style?.hidden === true ? null : (_jsxs("mesh", { "position-y": 0.5 * scaleY, scale: [scaleX, scaleY, scaleZ], children: [_jsx("boxGeometry", { args: [1, 1, 1] }), _jsx("meshStandardMaterial", { color: color, transparent: opacity < 1, opacity: opacity })] })) }));
|
|
319
370
|
}
|
|
320
371
|
function GroundPlane() {
|
|
321
372
|
const geometry = useMemo(() => {
|
|
@@ -348,7 +399,12 @@ function RockField() {
|
|
|
348
399
|
}), []);
|
|
349
400
|
return (_jsx(_Fragment, { children: rocks.map((rock) => (_jsxs("mesh", { position: [rock.x, 0.25 * rock.scale, rock.z], rotation: [0.1, rock.rotation, -0.08], scale: [rock.scale * 1.4, rock.scale * 0.7, rock.scale], children: [_jsx("dodecahedronGeometry", { args: [0.8, 0] }), _jsx("meshStandardMaterial", { color: "#6b6f63", roughness: 1 })] }, rock.id))) }));
|
|
350
401
|
}
|
|
351
|
-
function
|
|
402
|
+
function WorldEnvironment({ environment: Environment }) {
|
|
403
|
+
if (Environment !== undefined)
|
|
404
|
+
return _jsx(Environment, {});
|
|
405
|
+
return (_jsxs(_Fragment, { children: [_jsx(GroundPlane, {}), _jsx("gridHelper", { args: [160, 80, "#3a3f4a", "#2b2f38"], "position-y": 0.01 }), _jsx(RockField, {})] }));
|
|
406
|
+
}
|
|
407
|
+
function WorldActors({ entitySprites, entityModels, objectModels, objectStyles, assets, renderEntity, renderObject, selectedIds, }) {
|
|
352
408
|
const ctx = useGameContext();
|
|
353
409
|
const entities = useSceneEntities();
|
|
354
410
|
const objects = useSceneObjects();
|
|
@@ -359,18 +415,28 @@ function WorldView({ entitySprites, entityModels, objectModels, objectStyles, en
|
|
|
359
415
|
const relation = ctx.scene.entity.canReceive(entity.id, "damage") === null ? "hostile" : "friendly";
|
|
360
416
|
ctx.scene.entity.setTarget(controlledId, relation === "hostile" || entity.role === "npc" ? entity.id : null);
|
|
361
417
|
};
|
|
362
|
-
return (_jsxs(_Fragment, { children: [
|
|
418
|
+
return (_jsxs(_Fragment, { children: [entities
|
|
363
419
|
.filter((entity) => entity.name !== WORLD_ITEM_ENTITY_NAME)
|
|
364
|
-
.map((entity) => (_jsx(EntityMarker, { entity: entity, custom: renderEntity?.(entity), model: resolveModel(entityModels?.[entity.name], assets
|
|
365
|
-
|
|
366
|
-
|
|
420
|
+
.map((entity) => (_jsx(EntityMarker, { entity: entity, custom: renderEntity?.(entity), model: resolveModel(entityModels?.[entity.name], assets, {
|
|
421
|
+
seam: "entityModels",
|
|
422
|
+
key: entity.name,
|
|
423
|
+
}), sprite: entitySprites?.[entity.name], isLocal: entity.id === controlledId, targeted: entity.id === targetId, selected: selectedIds.has(entity.id), onSelect: handleSelect }, entity.id))), objects.map((object) => {
|
|
424
|
+
const model = resolveModel(objectModels?.[object.catalogId], assets, {
|
|
425
|
+
seam: "objectModels",
|
|
426
|
+
key: object.catalogId,
|
|
427
|
+
}) ?? tryResolveCatalogModel(object.catalogId, assets);
|
|
367
428
|
return (_jsx(ObjectMarker, { object: object, custom: renderObject?.(object), model: model, style: objectStyles?.[object.catalogId] }, object.instanceId));
|
|
368
429
|
})] }));
|
|
369
430
|
}
|
|
431
|
+
function WorldView({ entitySprites, entityModels, objectModels, objectStyles, environment, assets, renderEntity, renderObject, selectedIds, }) {
|
|
432
|
+
return (_jsxs(_Fragment, { children: [_jsx(WorldEnvironment, { environment: environment }), _jsx(WorldActors, { entitySprites: entitySprites, entityModels: entityModels, objectModels: objectModels, objectStyles: objectStyles, assets: assets, renderEntity: renderEntity, renderObject: renderObject, selectedIds: selectedIds })] }));
|
|
433
|
+
}
|
|
370
434
|
function RemotePlayers({ rows }) {
|
|
371
435
|
return (_jsx(_Fragment, { children: rows.map((row) => (_jsxs("group", { position: [row.position.x, row.position.y, row.position.z], "rotation-y": row.rotationY, children: [_jsxs("mesh", { "position-y": 0.95, children: [_jsx("capsuleGeometry", { args: [0.35, 1.1, 6, 14] }), _jsx("meshStandardMaterial", { color: colorFromId(row.userId) })] }), _jsxs("mesh", { position: [0, 1.35, 0.32], children: [_jsx("boxGeometry", { args: [0.16, 0.16, 0.16] }), _jsx("meshStandardMaterial", { color: "#f8fafc" })] })] }, row.userId))) }));
|
|
372
436
|
}
|
|
373
|
-
function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryClickRef, onRuntimeError, multiplayer, serverIdRef, pointerService, pointerAim, pingCommand, }) {
|
|
437
|
+
function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryClickRef, pointerAxisRef, onRuntimeError, multiplayer, serverIdRef, pointerService, pointerAim, pingCommand, poster, onPosterSettled, }) {
|
|
438
|
+
const posterElapsedRef = useRef(0);
|
|
439
|
+
const posterDoneRef = useRef(false);
|
|
374
440
|
const motionRef = useRef(createPlayerMotionState());
|
|
375
441
|
const voxelBodyRef = useRef(null);
|
|
376
442
|
const solidCacheRef = useRef({ count: -1, set: new Set() });
|
|
@@ -381,9 +447,15 @@ function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryClickRef
|
|
|
381
447
|
const collision = playable.collision;
|
|
382
448
|
const movement = playable.movement;
|
|
383
449
|
const voxelDims = useMemo(() => ({
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
450
|
+
get halfWidth() {
|
|
451
|
+
return collision?.halfWidth ?? 0.3;
|
|
452
|
+
},
|
|
453
|
+
get height() {
|
|
454
|
+
return collision?.height ?? 1.8;
|
|
455
|
+
},
|
|
456
|
+
get stepHeight() {
|
|
457
|
+
return collision?.stepHeight ?? 0.6;
|
|
458
|
+
},
|
|
387
459
|
}), [collision]);
|
|
388
460
|
const movementTuning = useMemo(() => resolvePhysicsTuning(playable.game.physics), [playable]);
|
|
389
461
|
const autoPickupRadius = useMemo(() => {
|
|
@@ -398,26 +470,39 @@ function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryClickRef
|
|
|
398
470
|
const inputActions = useMemo(() => Object.keys(playable.game.input ?? {}), [playable]);
|
|
399
471
|
const hasTerrain = useMemo(() => hasEnvironmentTerrain(playable.game.world), [playable]);
|
|
400
472
|
useFrame((_state, rawDt) => {
|
|
473
|
+
if (poster) {
|
|
474
|
+
if (posterDoneRef.current)
|
|
475
|
+
return;
|
|
476
|
+
posterElapsedRef.current += Math.min(rawDt, 0.05);
|
|
477
|
+
if (posterElapsedRef.current >= POSTER_SETTLE_SECONDS) {
|
|
478
|
+
posterDoneRef.current = true;
|
|
479
|
+
onPosterSettled();
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
401
483
|
const simStart = performance.now();
|
|
402
484
|
try {
|
|
485
|
+
let endPhase = devtools.profile.begin("time+input");
|
|
403
486
|
const dt = Math.min(rawDt, 0.05);
|
|
404
487
|
const gameDt = ctx.time.advance(dt);
|
|
405
488
|
ctx.input.publish(heldActionsFor(tracker, inputActions));
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if (
|
|
409
|
-
yawRef.current
|
|
489
|
+
ctx.input.publishPointer(pointerAxisRef.current);
|
|
490
|
+
const turnInput = (tracker.isDown("turnRight") ? 1 : 0) - (tracker.isDown("turnLeft") ? 1 : 0);
|
|
491
|
+
if (turnInput !== 0)
|
|
492
|
+
yawRef.current = steerYaw(yawRef.current, turnInput, TURN_SPEED, dt);
|
|
493
|
+
endPhase();
|
|
410
494
|
const playerId = ctx.player.possession.active(ctx.player.userId);
|
|
411
495
|
const player = ctx.scene.entity.get(playerId);
|
|
412
496
|
const forwardX = Math.sin(yawRef.current);
|
|
413
497
|
const forwardZ = Math.cos(yawRef.current);
|
|
414
498
|
if (player !== null && drivesPose) {
|
|
499
|
+
endPhase = devtools.profile.begin("pose");
|
|
415
500
|
const keys = createEmptyMovementKeys();
|
|
416
501
|
keys.w = tracker.isDown("moveForward");
|
|
417
502
|
keys.s = tracker.isDown("moveBack");
|
|
418
503
|
keys.a = tracker.isDown("moveLeft");
|
|
419
504
|
keys.d = tracker.isDown("moveRight");
|
|
420
|
-
keys.shift = tracker.isDown("sprint");
|
|
505
|
+
keys.shift = tracker.isDown("sprint") && (movement?.canSprint?.(ctx) ?? true);
|
|
421
506
|
keys.space = tracker.isDown("jump");
|
|
422
507
|
const intent = resolveMovementIntent(keys, true);
|
|
423
508
|
const motionBatch = ctx.player.motion.takePending();
|
|
@@ -436,6 +521,7 @@ function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryClickRef
|
|
|
436
521
|
const solids = cache.set;
|
|
437
522
|
const isSolid = (x, y, z) => solids.has(`${x},${y},${z}`);
|
|
438
523
|
body.velocityY = applyMotionImpulses(body.velocityY, motionBatch);
|
|
524
|
+
[body.velocityX, body.velocityZ] = applyHorizontalImpulses(body.velocityX, body.velocityZ, motionBatch);
|
|
439
525
|
advanceVoxelPlayer(body, intent, forwardX, forwardZ, player.movement.walkSpeed ?? 2, rawDt, isSolid, voxelDims, movementTuning, hasTerrain ? (x, z) => ground.sampleHeight(x, z) : undefined);
|
|
440
526
|
if (motionBatch !== null && motionBatch.y !== null)
|
|
441
527
|
body.y = motionBatch.y;
|
|
@@ -450,6 +536,7 @@ function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryClickRef
|
|
|
450
536
|
else {
|
|
451
537
|
const motion = motionRef.current;
|
|
452
538
|
motion.verticalVelocity = applyMotionImpulses(motion.verticalVelocity, motionBatch);
|
|
539
|
+
[motion.horizontalVelocityX, motion.horizontalVelocityZ] = applyHorizontalImpulses(motion.horizontalVelocityX, motion.horizontalVelocityZ, motionBatch);
|
|
453
540
|
const step = advancePlayerMotion(motion, intent, forwardX, forwardZ, player.movement.walkSpeed ?? 2, rawDt, movementTuning);
|
|
454
541
|
let stepX = step.stepX;
|
|
455
542
|
let stepZ = step.stepZ;
|
|
@@ -482,6 +569,7 @@ function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryClickRef
|
|
|
482
569
|
current: player.position,
|
|
483
570
|
next: [nextX, nextY, nextZ],
|
|
484
571
|
dt: rawDt,
|
|
572
|
+
ctx,
|
|
485
573
|
};
|
|
486
574
|
const replacement = movement.beforeCommit(frame);
|
|
487
575
|
if (replacement !== undefined) {
|
|
@@ -498,16 +586,22 @@ function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryClickRef
|
|
|
498
586
|
dt: rawDt,
|
|
499
587
|
});
|
|
500
588
|
}
|
|
589
|
+
endPhase();
|
|
501
590
|
}
|
|
502
591
|
if (autoPickupRadius !== null) {
|
|
592
|
+
endPhase = devtools.profile.begin("pickup");
|
|
503
593
|
const self = ctx.scene.entity.get(playerId);
|
|
504
594
|
if (self !== null) {
|
|
505
595
|
const nearest = ctx.scene.worldItem.nearestInRadius(self.position, autoPickupRadius);
|
|
506
596
|
if (nearest !== null)
|
|
507
597
|
ctx.scene.worldItem.pickup(nearest, ctx.player.userId);
|
|
508
598
|
}
|
|
599
|
+
endPhase();
|
|
509
600
|
}
|
|
510
|
-
|
|
601
|
+
devtools.profile.measure("onTick", () => {
|
|
602
|
+
playable.loop.onTick(ctx, gameDt);
|
|
603
|
+
});
|
|
604
|
+
endPhase = devtools.profile.begin("actions");
|
|
511
605
|
if (tracker.wasPressed("tabTarget")) {
|
|
512
606
|
if (ctx.game.commands.has("target.cycle"))
|
|
513
607
|
ctx.game.commands.run("target.cycle", {});
|
|
@@ -533,6 +627,18 @@ function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryClickRef
|
|
|
533
627
|
}
|
|
534
628
|
const aimOverride = pointerAim ? pointerAimFor(ctx, pointerService) : undefined;
|
|
535
629
|
const commandAim = aimOverride ?? { yaw: yawRef.current, pitch: pitchRef.current };
|
|
630
|
+
if (collisionDebug.getState().layers.aimLaser) {
|
|
631
|
+
const aimFrom = ctx.player.possession.active(playerId) ?? playerId;
|
|
632
|
+
collisionDebug.setAimProbe({
|
|
633
|
+
from: aimFrom,
|
|
634
|
+
aim: commandAim,
|
|
635
|
+
originPolicy: { kind: "muzzle" },
|
|
636
|
+
maxDistance: 100,
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
else if (collisionDebug.getAimProbe() !== null) {
|
|
640
|
+
collisionDebug.setAimProbe(null);
|
|
641
|
+
}
|
|
536
642
|
const nowMs = performance.now();
|
|
537
643
|
for (const action of Object.keys(playable.game.input ?? {})) {
|
|
538
644
|
const pressed = tracker.wasPressed(action);
|
|
@@ -580,8 +686,10 @@ function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryClickRef
|
|
|
580
686
|
}
|
|
581
687
|
}
|
|
582
688
|
tracker.endFrame();
|
|
689
|
+
endPhase();
|
|
583
690
|
const serverId = serverIdRef.current;
|
|
584
691
|
if (multiplayer !== null && serverId !== null) {
|
|
692
|
+
endPhase = devtools.profile.begin("presence");
|
|
585
693
|
const focus = ctx.scene.entity.get(playerId);
|
|
586
694
|
if (focus !== null) {
|
|
587
695
|
multiplayer.backend.presenceSync.syncPose(serverId, {
|
|
@@ -592,6 +700,7 @@ function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryClickRef
|
|
|
592
700
|
rotationPitch: pitchRef.current,
|
|
593
701
|
});
|
|
594
702
|
}
|
|
703
|
+
endPhase();
|
|
595
704
|
}
|
|
596
705
|
}
|
|
597
706
|
catch (error) {
|
|
@@ -604,7 +713,7 @@ function FrameDriver({ ctx, playable, tracker, yawRef, pitchRef, primaryClickRef
|
|
|
604
713
|
}, GAME_SIM_FRAME_PRIORITY);
|
|
605
714
|
return null;
|
|
606
715
|
}
|
|
607
|
-
function HudOnlyDriver({ ctx, playable, tracker, onRuntimeError, }) {
|
|
716
|
+
function HudOnlyDriver({ ctx, playable, tracker, pointerAxisRef, onRuntimeError, }) {
|
|
608
717
|
const hasReportedTickError = useRef(false);
|
|
609
718
|
const repeatFiredAtRef = useRef(new Map());
|
|
610
719
|
const lastFrameRef = useRef(null);
|
|
@@ -620,10 +729,16 @@ function HudOnlyDriver({ ctx, playable, tracker, onRuntimeError, }) {
|
|
|
620
729
|
const rawDt = (now - last) / 1000;
|
|
621
730
|
const simStart = performance.now();
|
|
622
731
|
try {
|
|
732
|
+
let endPhase = devtools.profile.begin("time+input");
|
|
623
733
|
const dt = Math.min(rawDt, 0.05);
|
|
624
734
|
const gameDt = ctx.time.advance(dt);
|
|
625
735
|
ctx.input.publish(heldActionsFor(tracker, inputActions));
|
|
626
|
-
|
|
736
|
+
ctx.input.publishPointer(pointerAxisRef.current);
|
|
737
|
+
endPhase();
|
|
738
|
+
devtools.profile.measure("onTick", () => {
|
|
739
|
+
playable.loop.onTick(ctx, gameDt);
|
|
740
|
+
});
|
|
741
|
+
endPhase = devtools.profile.begin("actions");
|
|
627
742
|
const nowMs = performance.now();
|
|
628
743
|
for (const action of inputActions) {
|
|
629
744
|
if (!shouldFireBoundAction(tracker, action, playable.game.input, repeatFiredAtRef.current, nowMs))
|
|
@@ -632,6 +747,7 @@ function HudOnlyDriver({ ctx, playable, tracker, onRuntimeError, }) {
|
|
|
632
747
|
dispatchBoundAction(ctx, action, 0, 0, { yaw: 0, pitch: 0 });
|
|
633
748
|
}
|
|
634
749
|
tracker.endFrame();
|
|
750
|
+
endPhase();
|
|
635
751
|
}
|
|
636
752
|
catch (error) {
|
|
637
753
|
if (!hasReportedTickError.current) {
|
|
@@ -643,7 +759,7 @@ function HudOnlyDriver({ ctx, playable, tracker, onRuntimeError, }) {
|
|
|
643
759
|
};
|
|
644
760
|
frameId = requestAnimationFrame(tick);
|
|
645
761
|
return () => cancelAnimationFrame(frameId);
|
|
646
|
-
}, [ctx, playable, tracker, onRuntimeError, inputActions]);
|
|
762
|
+
}, [ctx, playable, tracker, pointerAxisRef, onRuntimeError, inputActions]);
|
|
647
763
|
return null;
|
|
648
764
|
}
|
|
649
765
|
class GameUiErrorBoundary extends Component {
|
|
@@ -651,8 +767,8 @@ class GameUiErrorBoundary extends Component {
|
|
|
651
767
|
static getDerivedStateFromError() {
|
|
652
768
|
return { failed: true };
|
|
653
769
|
}
|
|
654
|
-
componentDidCatch(error) {
|
|
655
|
-
this.props.onRuntimeError(error, "ui-render");
|
|
770
|
+
componentDidCatch(error, info) {
|
|
771
|
+
this.props.onRuntimeError(error, "ui-render", info.componentStack ?? undefined);
|
|
656
772
|
}
|
|
657
773
|
render() {
|
|
658
774
|
if (this.state.failed)
|
|
@@ -660,16 +776,53 @@ class GameUiErrorBoundary extends Component {
|
|
|
660
776
|
return this.props.children;
|
|
661
777
|
}
|
|
662
778
|
}
|
|
663
|
-
function
|
|
779
|
+
function expandedReactError(message) {
|
|
780
|
+
return /Minified React error #185\b/.test(message)
|
|
781
|
+
? "Maximum update depth exceeded. A component is repeatedly updating state during render or after every update."
|
|
782
|
+
: null;
|
|
783
|
+
}
|
|
784
|
+
function diagnosticReport(diagnostic, gameName) {
|
|
785
|
+
const explanation = expandedReactError(diagnostic.message);
|
|
786
|
+
return [
|
|
787
|
+
"JGengine runtime error",
|
|
788
|
+
`Game: ${gameName}`,
|
|
789
|
+
`Engine: ${VERSION}`,
|
|
790
|
+
`Phase: ${diagnostic.phase}`,
|
|
791
|
+
`Time: ${diagnostic.capturedAt}`,
|
|
792
|
+
`Page: ${window.location.origin}${window.location.pathname}`,
|
|
793
|
+
`Browser: ${navigator.userAgent}`,
|
|
794
|
+
explanation === null ? null : `Explanation: ${explanation}`,
|
|
795
|
+
"",
|
|
796
|
+
`Message: ${diagnostic.message}`,
|
|
797
|
+
diagnostic.stack === undefined ? null : `JavaScript stack:\n${diagnostic.stack}`,
|
|
798
|
+
diagnostic.componentStack === undefined ? null : `React component stack:\n${diagnostic.componentStack}`,
|
|
799
|
+
]
|
|
800
|
+
.filter((line) => line !== null)
|
|
801
|
+
.join("\n");
|
|
802
|
+
}
|
|
803
|
+
function DiagnosticOverlay({ diagnostics, gameName }) {
|
|
804
|
+
const [copied, setCopied] = useState(false);
|
|
664
805
|
if (diagnostics.length === 0)
|
|
665
806
|
return null;
|
|
666
807
|
const latest = diagnostics[diagnostics.length - 1];
|
|
667
|
-
|
|
808
|
+
const explanation = expandedReactError(latest.message);
|
|
809
|
+
const report = diagnosticReport(latest, gameName);
|
|
810
|
+
const issueBody = report.length > 8000 ? `${report.slice(0, 8000)}\n\n[Report truncated; use Copy error for the full report.]` : report;
|
|
811
|
+
const issueUrl = `https://github.com/Noisemaker111/jgengine/issues/new?title=${encodeURIComponent(`[BUG] ${latest.phase}: ${latest.message.slice(0, 100)}`)}&body=${encodeURIComponent(issueBody)}`;
|
|
812
|
+
return (_jsxs("div", { className: "pointer-events-auto absolute right-4 top-4 z-50 max-w-lg rounded border border-red-400/60 bg-red-950/95 p-3 text-xs text-red-50 shadow-2xl", children: [_jsx("div", { className: "mb-1 font-semibold uppercase tracking-wide text-red-200", children: "JG engine error" }), _jsxs("div", { className: "font-mono text-[11px] text-red-100", children: ["[", latest.phase, "] ", latest.message] }), explanation !== null ? _jsx("div", { className: "mt-2 text-red-100", children: explanation }) : null, latest.stack !== undefined ? (_jsx("pre", { className: "mt-2 max-h-36 overflow-auto whitespace-pre-wrap text-[10px] text-red-200/80", children: latest.stack })) : null, latest.componentStack !== undefined ? (_jsx("pre", { className: "mt-2 max-h-36 overflow-auto whitespace-pre-wrap text-[10px] text-red-200/80", children: latest.componentStack })) : null, _jsxs("div", { className: "mt-3 flex gap-2", children: [_jsx("button", { className: "rounded border border-red-300/50 bg-red-900 px-2 py-1 font-semibold hover:bg-red-800", type: "button", onClick: () => {
|
|
813
|
+
void navigator.clipboard.writeText(report).then(() => {
|
|
814
|
+
setCopied(true);
|
|
815
|
+
window.setTimeout(() => setCopied(false), 2000);
|
|
816
|
+
});
|
|
817
|
+
}, children: copied ? "Copied" : "Copy error" }), _jsx("a", { className: "rounded border border-red-300/50 bg-red-900 px-2 py-1 font-semibold hover:bg-red-800", href: issueUrl, rel: "noreferrer", target: "_blank", children: "File issue" })] })] }));
|
|
668
818
|
}
|
|
669
|
-
|
|
819
|
+
const POSTER_SETTLE_SECONDS = 1.6;
|
|
820
|
+
export function GamePlayerShell({ playable, multiplayer: rawMultiplayer = null, poster = false, onContextReady, }) {
|
|
670
821
|
const multiplayer = useMemo(() => (rawMultiplayer === null ? null : withDevtoolsLatency(rawMultiplayer)), [rawMultiplayer]);
|
|
671
|
-
const devtoolsEnabled = playable.devtools !== false;
|
|
822
|
+
const devtoolsEnabled = playable.devtools !== false && !poster;
|
|
672
823
|
const [devtoolsOpen, setDevtoolsOpen] = useState(false);
|
|
824
|
+
const [posterFrozen, setPosterFrozen] = useState(false);
|
|
825
|
+
const posterSettledRef = useRef(false);
|
|
673
826
|
const [ctx, setCtx] = useState(null);
|
|
674
827
|
const [diagnostics, setDiagnostics] = useState([]);
|
|
675
828
|
const [remotePlayers, setRemotePlayers] = useState([]);
|
|
@@ -680,25 +833,55 @@ export function GamePlayerShell({ playable, multiplayer: rawMultiplayer = null,
|
|
|
680
833
|
const cameraDraggingRef = useRef(false);
|
|
681
834
|
const primaryClickRef = useRef(false);
|
|
682
835
|
const pointerDownRef = useRef(null);
|
|
836
|
+
const pointerAxisRef = useRef(null);
|
|
683
837
|
const marqueeStartRef = useRef(null);
|
|
838
|
+
const f2HeldRef = useRef(false);
|
|
839
|
+
const f2ChordedRef = useRef(false);
|
|
684
840
|
const pointerService = useMemo(() => createPointerService(), []);
|
|
685
841
|
const selection = useMemo(() => createSelectionSet(), [playable]);
|
|
686
842
|
const [marquee, setMarquee] = useState(null);
|
|
687
843
|
const [selectedIds, setSelectedIds] = useState(() => new Set());
|
|
688
844
|
const [contextMenu, setContextMenu] = useState(null);
|
|
689
|
-
const
|
|
845
|
+
const settingsStore = useMemo(() => createSettingsStore(), []);
|
|
846
|
+
const [bindingOverrides, setBindingOverrides] = useState(() => loadBindingOverrides(playable.game.name));
|
|
847
|
+
useEffect(() => {
|
|
848
|
+
setBindingOverrides(loadBindingOverrides(playable.game.name));
|
|
849
|
+
}, [playable]);
|
|
850
|
+
const effectiveInput = useMemo(() => applyBindingOverrides(playable.game.input ?? {}, bindingOverrides), [playable, bindingOverrides]);
|
|
851
|
+
const rebindAction = useCallback((action, code) => setBindingOverrides(saveBindingOverride(playable.game.name, action, [code])), [playable]);
|
|
852
|
+
const resetActionBinding = useCallback((action) => setBindingOverrides(clearBindingOverride(playable.game.name, action)), [playable]);
|
|
853
|
+
const tracker = useMemo(() => createActionStateTracker(toActionStateBindingMap(withTouchCodes(effectiveInput))), [effectiveInput]);
|
|
854
|
+
const graphics = useGraphicsSettings(settingsStore, playable.shadows ?? true);
|
|
855
|
+
const trackPointerAxis = (event) => {
|
|
856
|
+
const rect = wrapperRef.current?.getBoundingClientRect();
|
|
857
|
+
if (rect === undefined)
|
|
858
|
+
return;
|
|
859
|
+
pointerAxisRef.current = normalizePointerToAxis(event.clientX, event.clientY, rect);
|
|
860
|
+
};
|
|
861
|
+
const deactivatePointerAxis = () => {
|
|
862
|
+
const state = pointerAxisRef.current;
|
|
863
|
+
if (state !== null && state.active)
|
|
864
|
+
pointerAxisRef.current = { ...state, active: false };
|
|
865
|
+
};
|
|
690
866
|
const touchScheme = useMemo(() => deriveTouchScheme(playable.game.input, {
|
|
691
867
|
reserved: RESERVED_INPUT_ACTIONS,
|
|
692
868
|
firstPerson: resolveRigKind(playable.camera) === "first",
|
|
693
869
|
config: playable.touch,
|
|
694
870
|
}), [playable]);
|
|
695
|
-
const { coarsePointer } = useDisplayProfile();
|
|
871
|
+
const { coarsePointer, portrait, compact } = useDisplayProfile();
|
|
696
872
|
const touchSink = useMemo(() => ({ onCodeDown: (code) => tracker.handleDown(code), onCodeUp: (code) => tracker.handleUp(code) }), [tracker]);
|
|
697
873
|
const audioEngine = useMemo(() => createAudioEngine({ sounds: playable.audio?.sounds, buses: playable.audio?.buses }), [playable]);
|
|
698
874
|
useEffect(() => () => audioEngine.dispose(), [audioEngine]);
|
|
875
|
+
useEffect(() => {
|
|
876
|
+
if (ctx === null)
|
|
877
|
+
return;
|
|
878
|
+
return ctx.game.events.on("audio.play", ({ sound, at }) => {
|
|
879
|
+
audioEngine.playOneShot(sound, at === undefined ? undefined : { x: at[0], y: at[1], z: at[2] });
|
|
880
|
+
});
|
|
881
|
+
}, [ctx, audioEngine]);
|
|
699
882
|
const userId = multiplayer?.userId ?? DEV_USER_ID;
|
|
700
|
-
const reportRuntimeError = (error, phase) => {
|
|
701
|
-
const diagnostic = logRuntimeError(error, phase);
|
|
883
|
+
const reportRuntimeError = (error, phase, componentStack) => {
|
|
884
|
+
const diagnostic = logRuntimeError(error, phase, componentStack);
|
|
702
885
|
setDiagnostics((current) => [...current.slice(-4), { ...diagnostic, id: Date.now() + current.length }]);
|
|
703
886
|
};
|
|
704
887
|
useEffect(() => {
|
|
@@ -712,6 +895,7 @@ export function GamePlayerShell({ playable, multiplayer: rawMultiplayer = null,
|
|
|
712
895
|
});
|
|
713
896
|
playable.loop.onInit(context);
|
|
714
897
|
playable.loop.onNewPlayer(context);
|
|
898
|
+
onContextReady?.(context);
|
|
715
899
|
setCtx(context);
|
|
716
900
|
}
|
|
717
901
|
catch (error) {
|
|
@@ -829,13 +1013,29 @@ export function GamePlayerShell({ playable, multiplayer: rawMultiplayer = null,
|
|
|
829
1013
|
return (_jsxs("div", { ref: wrapperRef, tabIndex: 0, className: "relative h-full w-full bg-neutral-950 outline-none", onKeyDown: (event) => {
|
|
830
1014
|
if (event.code === "F2" && devtoolsEnabled) {
|
|
831
1015
|
event.preventDefault();
|
|
832
|
-
|
|
1016
|
+
f2HeldRef.current = true;
|
|
1017
|
+
f2ChordedRef.current = false;
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
if (f2HeldRef.current) {
|
|
1021
|
+
f2ChordedRef.current = true;
|
|
833
1022
|
return;
|
|
834
1023
|
}
|
|
835
1024
|
if (event.code === "Tab" || event.code === "Space")
|
|
836
1025
|
event.preventDefault();
|
|
837
1026
|
tracker.handleDown(event.code);
|
|
838
|
-
}, onKeyUp: (event) =>
|
|
1027
|
+
}, onKeyUp: (event) => {
|
|
1028
|
+
if (event.code === "F2" && devtoolsEnabled) {
|
|
1029
|
+
f2HeldRef.current = false;
|
|
1030
|
+
if (!f2ChordedRef.current)
|
|
1031
|
+
setDevtoolsOpen((current) => !current);
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
tracker.handleUp(event.code);
|
|
1035
|
+
}, onBlur: () => {
|
|
1036
|
+
f2HeldRef.current = false;
|
|
1037
|
+
tracker.reset();
|
|
1038
|
+
}, onPointerMove: trackPointerAxis, onPointerLeave: deactivatePointerAxis, onPointerCancel: deactivatePointerAxis, children: [_jsx(HudOnlyDriver, { ctx: ctx, playable: playable, tracker: tracker, pointerAxisRef: pointerAxisRef, onRuntimeError: reportRuntimeError }), _jsx(GameUiErrorBoundary, { onRuntimeError: reportRuntimeError, children: _jsx(GameProvider, { context: ctx, children: _jsx(HudViewportProvider, { platforms: playable.platforms, config: playable.hudFit, userScale: graphics.uiScale, children: _jsx(GameUI, {}) }) }) }), devtoolsEnabled ? (_jsx(DevtoolsOverlay, { open: devtoolsOpen, ctx: ctx, playable: playable, multiplayer: multiplayer })) : null, _jsx(DiagnosticOverlay, { diagnostics: diagnostics, gameName: playable.game.name })] }));
|
|
839
1039
|
}
|
|
840
1040
|
const firstPerson = rigKind === "first";
|
|
841
1041
|
const showReticle = (firstPerson && playable.camera?.firstPerson?.reticle !== false) || rigKind === "shoulder";
|
|
@@ -864,6 +1064,7 @@ export function GamePlayerShell({ playable, multiplayer: rawMultiplayer = null,
|
|
|
864
1064
|
const effectiveSky = backdropSky ?? worldSky;
|
|
865
1065
|
const backgroundColor = backdrop?.background ?? (effectiveSky === undefined ? DEFAULT_BACKGROUND_COLOR : undefined);
|
|
866
1066
|
const lighting = playable.lighting;
|
|
1067
|
+
const orthographic = playable.camera?.projection === "orthographic";
|
|
867
1068
|
const localXY = (event) => {
|
|
868
1069
|
const rect = wrapperRef.current?.getBoundingClientRect();
|
|
869
1070
|
return rect === undefined
|
|
@@ -887,6 +1088,7 @@ export function GamePlayerShell({ playable, multiplayer: rawMultiplayer = null,
|
|
|
887
1088
|
};
|
|
888
1089
|
const handlePointerDown = (event) => {
|
|
889
1090
|
wrapperRef.current?.focus();
|
|
1091
|
+
trackPointerAxis(event);
|
|
890
1092
|
audioEngine.resume();
|
|
891
1093
|
if (contextMenu !== null)
|
|
892
1094
|
setContextMenu(null);
|
|
@@ -898,6 +1100,7 @@ export function GamePlayerShell({ playable, multiplayer: rawMultiplayer = null,
|
|
|
898
1100
|
}
|
|
899
1101
|
};
|
|
900
1102
|
const handlePointerMove = (event) => {
|
|
1103
|
+
trackPointerAxis(event);
|
|
901
1104
|
if (pointer?.select !== true)
|
|
902
1105
|
return;
|
|
903
1106
|
const start = marqueeStartRef.current;
|
|
@@ -996,33 +1199,90 @@ export function GamePlayerShell({ playable, multiplayer: rawMultiplayer = null,
|
|
|
996
1199
|
ctx.game.commands.run(verb.command, contextVerbInput(state.menu, verb));
|
|
997
1200
|
}
|
|
998
1201
|
};
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1202
|
+
const controlsActive = playControlsActive(ctx);
|
|
1203
|
+
const settingsDisabled = playable.settings === false;
|
|
1204
|
+
const settingsConfig = playable.settings === false || playable.settings === undefined ? {} : playable.settings;
|
|
1205
|
+
const settingsSurface = settingsDisabled ? false : settingsConfig.surface ?? false;
|
|
1206
|
+
const settingsVariant = settingsConfig.variant ?? "panel";
|
|
1207
|
+
const hideCategories = settingsDisabled ? BUILT_IN_SETTING_CATEGORIES : settingsConfig.hide ?? [];
|
|
1208
|
+
const fovControlEnabled = !orthographic && playable.camera?.playerFov?.control !== false;
|
|
1209
|
+
const settingsHostsFov = !settingsDisabled && !hideCategories.includes("gameplay") && fovControlEnabled;
|
|
1210
|
+
const settingsActions = settingsDisabled
|
|
1211
|
+
? []
|
|
1212
|
+
: (settingsConfig.actions ?? []).map((action) => ({
|
|
1213
|
+
id: action.id,
|
|
1214
|
+
label: action.label,
|
|
1215
|
+
kind: action.kind ?? "default",
|
|
1216
|
+
description: action.description,
|
|
1217
|
+
run: () => action.run(ctx),
|
|
1218
|
+
}));
|
|
1219
|
+
const touchScale = compact ? 0.88 : 1;
|
|
1220
|
+
const dockMounted = !poster &&
|
|
1221
|
+
coarsePointer &&
|
|
1222
|
+
controlsActive &&
|
|
1223
|
+
touchScheme !== null &&
|
|
1224
|
+
(touchScheme.joystick !== null || touchScheme.buttons.length > 0);
|
|
1225
|
+
const orientationMismatch = !poster &&
|
|
1226
|
+
coarsePointer &&
|
|
1227
|
+
playable.orientation !== undefined &&
|
|
1228
|
+
(playable.orientation === "landscape") === portrait;
|
|
1229
|
+
return (_jsx(SettingsProvider, { store: settingsStore, children: _jsxs(PlayerFovProvider, { config: playable.camera, orthographic: orthographic, children: [_jsx(AudioSettingsBridge, { store: settingsStore, engine: audioEngine, buses: playable.audio?.buses }), _jsx(SettingsRuntime, { variant: settingsVariant, surface: settingsSurface, actions: settingsActions, input: playable.game.input ?? {}, buses: playable.audio?.buses, extra: settingsConfig.extra ?? [], categories: settingsConfig.categories ?? [], hide: hideCategories, fovEnabled: fovControlEnabled, hideBindings: settingsConfig.hideBindings ?? [], overrides: bindingOverrides, rebind: rebindAction, resetBinding: resetActionBinding, children: _jsxs("div", { ref: wrapperRef, tabIndex: 0, ...(poster && posterFrozen ? { "data-poster-ready": "" } : {}), className: "relative h-full w-full bg-neutral-950 outline-none", style: {
|
|
1230
|
+
"--jg-hud-dock-clearance": `${dockMounted ? touchDockClearance(touchScheme, touchScale) : 0}px`,
|
|
1231
|
+
}, onKeyDown: (event) => {
|
|
1232
|
+
if (event.code === "F2" && devtoolsEnabled) {
|
|
1233
|
+
event.preventDefault();
|
|
1234
|
+
f2HeldRef.current = true;
|
|
1235
|
+
f2ChordedRef.current = false;
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
if (f2HeldRef.current) {
|
|
1239
|
+
f2ChordedRef.current = true;
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
if (event.code === "Tab" || event.code === "Space")
|
|
1243
|
+
event.preventDefault();
|
|
1244
|
+
tracker.handleDown(event.code);
|
|
1245
|
+
}, onKeyUp: (event) => {
|
|
1246
|
+
if (event.code === "F2" && devtoolsEnabled) {
|
|
1247
|
+
f2HeldRef.current = false;
|
|
1248
|
+
if (!f2ChordedRef.current) {
|
|
1249
|
+
document.exitPointerLock?.();
|
|
1250
|
+
setDevtoolsOpen((current) => !current);
|
|
1251
|
+
}
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
tracker.handleUp(event.code);
|
|
1255
|
+
}, onBlur: () => {
|
|
1256
|
+
f2HeldRef.current = false;
|
|
1257
|
+
tracker.reset();
|
|
1258
|
+
}, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onPointerLeave: deactivatePointerAxis, onPointerCancel: deactivatePointerAxis, onContextMenu: handleContextMenu, onWheel: (event) => {
|
|
1259
|
+
if (ctx === null || !event.shiftKey)
|
|
1260
|
+
return;
|
|
1261
|
+
event.preventDefault();
|
|
1262
|
+
if (event.deltaY < 0 && ctx.game.commands.has("ui.hotbarScrollNext")) {
|
|
1263
|
+
ctx.game.commands.run("ui.hotbarScrollNext", {});
|
|
1264
|
+
}
|
|
1265
|
+
else if (event.deltaY > 0 && ctx.game.commands.has("ui.hotbarScrollPrev")) {
|
|
1266
|
+
ctx.game.commands.run("ui.hotbarScrollPrev", {});
|
|
1267
|
+
}
|
|
1268
|
+
}, children: [_jsxs(Canvas, { frameloop: poster && posterFrozen ? "demand" : "always", orthographic: orthographic, camera: orthographic
|
|
1269
|
+
? {
|
|
1270
|
+
zoom: playable.camera?.frustum?.zoom ?? CAMERA_FRUSTUM_DEFAULTS.zoom,
|
|
1271
|
+
near: playable.camera?.frustum?.near ?? CAMERA_FRUSTUM_DEFAULTS.near,
|
|
1272
|
+
far: playable.camera?.frustum?.far ?? CAMERA_FRUSTUM_DEFAULTS.far,
|
|
1273
|
+
}
|
|
1274
|
+
: {
|
|
1275
|
+
fov: playable.camera?.frustum?.fov ?? CAMERA_FRUSTUM_DEFAULTS.fov,
|
|
1276
|
+
near: playable.camera?.frustum?.near ?? CAMERA_FRUSTUM_DEFAULTS.near,
|
|
1277
|
+
far: playable.camera?.frustum?.far ?? CAMERA_FRUSTUM_DEFAULTS.far,
|
|
1278
|
+
}, shadows: graphics.shadows, dpr: graphics.dpr, gl: { preserveDrawingBuffer: true }, style: { touchAction: "none" }, children: [backgroundColor !== undefined ? _jsx("color", { attach: "background", args: [backgroundColor] }) : null, lighting !== undefined ? (_jsx(ConfiguredLighting, { lighting: lighting })) : effectiveSky === undefined ? (_jsxs(_Fragment, { children: [_jsx("ambientLight", { intensity: 0.55 }), _jsx("directionalLight", { position: [10, 16, 6], intensity: 1.3 })] })) : null, effectiveSky !== undefined ? (effectiveSky.timeOfDay ? (_jsx(TimeOfDayDaylight, { sky: effectiveSky, clock: ctx.time, lights: skyEmitsLights(resolveSkyLightOwnership(lighting !== undefined)) })) : (_jsx(SkyDaylight, { sky: effectiveSky, lights: skyEmitsLights(resolveSkyLightOwnership(lighting !== undefined)) }))) : null, _jsx(BackdropFog, { fog: backdrop?.fog }), _jsxs(GameProvider, { context: ctx, children: [_jsx(CullingProvider, { config: playable.visibility, children: _jsx(WorldView, { entitySprites: playable.entitySprites, entityModels: playable.entityModels, objectModels: playable.objectModels, objectStyles: playable.objectStyles, environment: AutoEnvironment, assets: playable.game.assets, renderEntity: playable.renderEntity, renderObject: playable.renderObject, selectedIds: selectedIds }) }), WorldOverlay !== undefined ? _jsx(WorldOverlay, {}) : null, barsStatId !== null ? (_jsx(WorldEntityBars, { statId: barsStatId, roles: barsRoles, resolveRole: resolveEntityRole })) : null, _jsx(WorldItems, { config: playable.worldItem }), _jsx(WorldTelegraphs, {}), _jsx(WorldFloatText, {}), _jsx(ProjectileTracers, {}), devtoolsEnabled ? _jsx(CollisionDebugWorld, {}) : null, _jsx(CombatCameraShake, {}), _jsx(AudioListener, { engine: audioEngine }), _jsx(EntityAudioEmitters, { engine: audioEngine, entitySounds: playable.entitySounds }), _jsx(ObjectAudioEmitters, { engine: audioEngine, objectSounds: playable.objectSounds }), _jsx(GameCameraRig, { yawRef: yawRef, pitchRef: pitchRef, config: cameraConfig, pointerControls: pointerUsesLeft, panKeysEnabled: rtsPanKeysEnabled, director: ctx.camera, onDragChange: (dragging) => {
|
|
1279
|
+
cameraDraggingRef.current = dragging;
|
|
1280
|
+
} }), _jsx(PointerProbe, { service: pointerService })] }), _jsx(RemotePlayers, { rows: remotePlayers }), _jsx(FrameDriver, { ctx: ctx, playable: playable, tracker: tracker, yawRef: yawRef, pitchRef: pitchRef, primaryClickRef: primaryClickRef, pointerAxisRef: pointerAxisRef, onRuntimeError: reportRuntimeError, multiplayer: multiplayer, serverIdRef: serverIdRef, pointerService: pointerService, pointerAim: pointer?.aim === true, pingCommand: pointer?.pingCommand, poster: poster, onPosterSettled: () => {
|
|
1281
|
+
if (posterSettledRef.current)
|
|
1282
|
+
return;
|
|
1283
|
+
posterSettledRef.current = true;
|
|
1284
|
+
setPosterFrozen(true);
|
|
1285
|
+
} }), _jsx(DevtoolsRendererProbe, {})] }), !poster && coarsePointer && controlsActive && touchScheme !== null && (touchScheme.gestures !== null || touchScheme.look) ? (_jsx(TouchPlaySurface, { scheme: touchScheme, sink: touchSink, yawRef: yawRef, pitchRef: pitchRef, maxPitch: playable.camera?.firstPerson?.maxPitch ?? 1.45, onPrimaryTap: () => {
|
|
1286
|
+
primaryClickRef.current = true;
|
|
1287
|
+
} })) : null, _jsx(GameUiErrorBoundary, { onRuntimeError: reportRuntimeError, children: _jsx(GameProvider, { context: ctx, children: _jsx(HudViewportProvider, { platforms: playable.platforms, config: playable.hudFit, userScale: graphics.uiScale, children: _jsx(GameUI, {}) }) }) }), !poster && showReticle ? _jsx(Reticle, {}) : null, dockMounted && touchScheme !== null ? (_jsx(TouchControlsDock, { scheme: touchScheme, sink: touchSink, scale: touchScale })) : null, orientationMismatch && playable.orientation !== undefined ? (_jsx(OrientationHint, { wanted: playable.orientation })) : null, marquee !== null ? _jsx(MarqueeBox, { rect: marquee }) : null, contextMenu !== null ? (_jsx(ContextMenuView, { menu: contextMenu.menu, x: contextMenu.x, y: contextMenu.y, onPick: handleVerbPick, onClose: () => setContextMenu(null) })) : null, devtoolsEnabled ? (_jsx(DevtoolsOverlay, { open: devtoolsOpen, ctx: ctx, playable: playable, multiplayer: multiplayer })) : null, poster ? null : _jsx(DiagnosticOverlay, { diagnostics: diagnostics, gameName: playable.game.name }), poster || orthographic || settingsHostsFov ? null : _jsx(PlayerFovSlider, {}), poster ? null : _jsx(SettingsChrome, {})] }) })] }) }));
|
|
1028
1288
|
}
|