@almadar/ui 5.95.0 → 5.97.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/dist/avl/index.cjs +1665 -1416
- package/dist/avl/index.js +660 -411
- package/dist/components/core/atoms/AtlasImage.d.ts +58 -0
- package/dist/components/core/atoms/index.d.ts +1 -0
- package/dist/components/game/2d/molecules/Canvas2D.d.ts +4 -0
- package/dist/components/game/2d/templates/GameShell.d.ts +19 -23
- package/dist/components/game/3d/index.cjs +893 -439
- package/dist/components/game/3d/index.js +726 -268
- package/dist/components/game/3d/molecules/GameCanvas3D.d.ts +34 -3
- package/dist/components/game/3d/molecules/ModelLoader.d.ts +9 -1
- package/dist/components/game/3d/molecules/index.d.ts +0 -1
- package/dist/components/game/shared/isometricTypes.d.ts +2 -0
- package/dist/components/index.cjs +1645 -1392
- package/dist/components/index.js +637 -387
- package/dist/marketing/index.cjs +208 -73
- package/dist/marketing/index.js +166 -28
- package/dist/providers/index.cjs +1533 -1284
- package/dist/providers/index.js +636 -387
- package/dist/runtime/index.cjs +1526 -1277
- package/dist/runtime/index.js +640 -391
- package/package.json +1 -1
- package/dist/components/game/3d/molecules/Canvas3D.d.ts +0 -45
- /package/dist/{components/game/shared → lib}/atlasSlice.d.ts +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React8 from 'react';
|
|
2
|
+
import React8__default, { forwardRef, useRef, useEffect, useImperativeHandle, useState, useMemo, useCallback, createContext, useContext, Component, useSyncExternalStore, useReducer } from 'react';
|
|
2
3
|
import { useThree, useFrame, Canvas, useLoader } from '@react-three/fiber';
|
|
3
4
|
import * as THREE6 from 'three';
|
|
4
5
|
import { Vector3, QuadraticBezierCurve3, MathUtils, Quaternion } from 'three';
|
|
5
6
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
6
|
-
import { OrbitControls, Billboard,
|
|
7
|
+
import { OrbitControls, Grid, Billboard, Text, Stars, Sparkles, Html, RoundedBox } from '@react-three/drei';
|
|
7
8
|
import { createLogger } from '@almadar/logger';
|
|
8
9
|
import { GLTFLoader as GLTFLoader$1 } from 'three/examples/jsm/loaders/GLTFLoader';
|
|
9
10
|
import { clone } from 'three/examples/jsm/utils/SkeletonUtils';
|
|
@@ -306,6 +307,36 @@ var Canvas3DErrorBoundary = class extends Component {
|
|
|
306
307
|
}
|
|
307
308
|
};
|
|
308
309
|
var log2 = createLogger("almadar:ui:game:model-loader");
|
|
310
|
+
var gltfCache = /* @__PURE__ */ new Map();
|
|
311
|
+
function loadGltfCached(url, assetRoot) {
|
|
312
|
+
const key = `${assetRoot}|${url}`;
|
|
313
|
+
let entry = gltfCache.get(key);
|
|
314
|
+
if (!entry) {
|
|
315
|
+
const pending = {
|
|
316
|
+
promise: new Promise((resolveDone) => {
|
|
317
|
+
const loader = new GLTFLoader$1();
|
|
318
|
+
loader.setResourcePath(assetRoot);
|
|
319
|
+
loader.load(
|
|
320
|
+
url,
|
|
321
|
+
(gltf) => {
|
|
322
|
+
log2.debug("Loaded", { url, clips: gltf.animations.length });
|
|
323
|
+
pending.gltf = { scene: gltf.scene, animations: gltf.animations };
|
|
324
|
+
resolveDone();
|
|
325
|
+
},
|
|
326
|
+
void 0,
|
|
327
|
+
(err) => {
|
|
328
|
+
log2.warn("Failed", { url, error: err instanceof Error ? err : String(err) });
|
|
329
|
+
pending.error = err instanceof Error ? err : new Error(String(err));
|
|
330
|
+
resolveDone();
|
|
331
|
+
}
|
|
332
|
+
);
|
|
333
|
+
})
|
|
334
|
+
};
|
|
335
|
+
entry = pending;
|
|
336
|
+
gltfCache.set(key, entry);
|
|
337
|
+
}
|
|
338
|
+
return entry;
|
|
339
|
+
}
|
|
309
340
|
function detectAssetRoot(modelUrl) {
|
|
310
341
|
const idx = modelUrl.indexOf("/3d/");
|
|
311
342
|
if (idx !== -1) {
|
|
@@ -313,42 +344,30 @@ function detectAssetRoot(modelUrl) {
|
|
|
313
344
|
}
|
|
314
345
|
return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
|
|
315
346
|
}
|
|
347
|
+
function stateFromEntry(entry) {
|
|
348
|
+
if (!entry) return { model: null, clips: [], isLoading: false, error: null };
|
|
349
|
+
if (entry.gltf) return { model: entry.gltf.scene, clips: entry.gltf.animations, isLoading: false, error: null };
|
|
350
|
+
return { model: null, clips: [], isLoading: !entry.error, error: entry.error ?? null };
|
|
351
|
+
}
|
|
316
352
|
function useGLTFModel(url, resourceBasePath) {
|
|
317
|
-
const [state, setState] = useState(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
error: null
|
|
321
|
-
});
|
|
353
|
+
const [state, setState] = useState(
|
|
354
|
+
() => stateFromEntry(url ? loadGltfCached(url, resourceBasePath || detectAssetRoot(url)) : null)
|
|
355
|
+
);
|
|
322
356
|
useEffect(() => {
|
|
323
357
|
if (!url) {
|
|
324
|
-
setState({ model: null, isLoading: false, error: null });
|
|
358
|
+
setState({ model: null, clips: [], isLoading: false, error: null });
|
|
325
359
|
return;
|
|
326
360
|
}
|
|
327
|
-
|
|
328
|
-
setState((
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
model: gltf.scene,
|
|
338
|
-
isLoading: false,
|
|
339
|
-
error: null
|
|
340
|
-
});
|
|
341
|
-
},
|
|
342
|
-
void 0,
|
|
343
|
-
(err) => {
|
|
344
|
-
log2.warn("Failed", { url, error: err instanceof Error ? err : String(err) });
|
|
345
|
-
setState({
|
|
346
|
-
model: null,
|
|
347
|
-
isLoading: false,
|
|
348
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
);
|
|
361
|
+
const entry = loadGltfCached(url, resourceBasePath || detectAssetRoot(url));
|
|
362
|
+
setState(stateFromEntry(entry));
|
|
363
|
+
if (entry.gltf || entry.error) return;
|
|
364
|
+
let cancelled = false;
|
|
365
|
+
void entry.promise.then(() => {
|
|
366
|
+
if (!cancelled) setState(stateFromEntry(entry));
|
|
367
|
+
});
|
|
368
|
+
return () => {
|
|
369
|
+
cancelled = true;
|
|
370
|
+
};
|
|
352
371
|
}, [url, resourceBasePath]);
|
|
353
372
|
return state;
|
|
354
373
|
}
|
|
@@ -364,21 +383,44 @@ function ModelLoader({
|
|
|
364
383
|
fallbackGeometry = "box",
|
|
365
384
|
castShadow = true,
|
|
366
385
|
receiveShadow = true,
|
|
367
|
-
resourceBasePath
|
|
386
|
+
resourceBasePath,
|
|
387
|
+
animation,
|
|
388
|
+
tint
|
|
368
389
|
}) {
|
|
369
|
-
const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
|
|
390
|
+
const { model: loadedModel, clips, isLoading, error } = useGLTFModel(url, resourceBasePath);
|
|
370
391
|
const model = useMemo(() => {
|
|
371
392
|
if (!loadedModel) return null;
|
|
372
393
|
const cloned = clone(loadedModel);
|
|
373
394
|
cloned.updateMatrixWorld(true);
|
|
395
|
+
const tintColor = tint ? new THREE6.Color(tint) : null;
|
|
374
396
|
cloned.traverse((child) => {
|
|
375
397
|
if (child instanceof THREE6.Mesh) {
|
|
376
398
|
child.castShadow = castShadow;
|
|
377
399
|
child.receiveShadow = receiveShadow;
|
|
400
|
+
if (tintColor && child.material instanceof THREE6.MeshStandardMaterial) {
|
|
401
|
+
const mat = child.material.clone();
|
|
402
|
+
mat.color.multiply(tintColor);
|
|
403
|
+
child.material = mat;
|
|
404
|
+
}
|
|
378
405
|
}
|
|
379
406
|
});
|
|
380
407
|
return cloned;
|
|
381
|
-
}, [loadedModel, castShadow, receiveShadow]);
|
|
408
|
+
}, [loadedModel, castShadow, receiveShadow, tint]);
|
|
409
|
+
const mixer = useMemo(() => model ? new THREE6.AnimationMixer(model) : null, [model]);
|
|
410
|
+
useEffect(() => {
|
|
411
|
+
if (!mixer || !animation || clips.length === 0) return;
|
|
412
|
+
const wanted = animation.toLowerCase();
|
|
413
|
+
const clip = clips.find((c) => c.name === animation) ?? clips.find((c) => c.name.toLowerCase() === wanted);
|
|
414
|
+
if (!clip) return;
|
|
415
|
+
const action = mixer.clipAction(clip);
|
|
416
|
+
action.reset().fadeIn(0.15).play();
|
|
417
|
+
return () => {
|
|
418
|
+
action.fadeOut(0.15);
|
|
419
|
+
};
|
|
420
|
+
}, [mixer, clips, animation]);
|
|
421
|
+
useFrame((_, delta) => {
|
|
422
|
+
mixer?.update(delta);
|
|
423
|
+
});
|
|
382
424
|
const normFactor = useMemo(() => {
|
|
383
425
|
if (!model) return 1;
|
|
384
426
|
const box = new THREE6.Box3().setFromObject(model);
|
|
@@ -1695,6 +1737,356 @@ function UnitSpriteBillboard({
|
|
|
1695
1737
|
)
|
|
1696
1738
|
] });
|
|
1697
1739
|
}
|
|
1740
|
+
function FollowCamera({
|
|
1741
|
+
target,
|
|
1742
|
+
offset
|
|
1743
|
+
}) {
|
|
1744
|
+
const { camera } = useThree();
|
|
1745
|
+
const look = useRef(new THREE6.Vector3(target[0], target[1], target[2]));
|
|
1746
|
+
const goal = useRef(new THREE6.Vector3());
|
|
1747
|
+
useFrame((_, delta) => {
|
|
1748
|
+
const t = Math.min(1, delta * 5);
|
|
1749
|
+
goal.current.set(target[0] + offset[0], target[1] + offset[1], target[2] + offset[2]);
|
|
1750
|
+
camera.position.lerp(goal.current, t);
|
|
1751
|
+
look.current.lerp(goal.current.set(target[0], target[1], target[2]), t);
|
|
1752
|
+
camera.lookAt(look.current);
|
|
1753
|
+
});
|
|
1754
|
+
return null;
|
|
1755
|
+
}
|
|
1756
|
+
function LerpedGroup({
|
|
1757
|
+
target,
|
|
1758
|
+
enabled,
|
|
1759
|
+
children
|
|
1760
|
+
}) {
|
|
1761
|
+
const ref = useRef(null);
|
|
1762
|
+
const goal = useRef(new THREE6.Vector3());
|
|
1763
|
+
useFrame((_, delta) => {
|
|
1764
|
+
const g = ref.current;
|
|
1765
|
+
if (!g) return;
|
|
1766
|
+
goal.current.set(target[0], target[1], target[2]);
|
|
1767
|
+
if (enabled) g.position.lerp(goal.current, Math.min(1, delta * 10));
|
|
1768
|
+
else g.position.copy(goal.current);
|
|
1769
|
+
});
|
|
1770
|
+
return /* @__PURE__ */ jsx("group", { ref, position: target, children });
|
|
1771
|
+
}
|
|
1772
|
+
var SIDE_PLATFORM_COLORS = {
|
|
1773
|
+
ground: "#5b8c3e",
|
|
1774
|
+
platform: "#8b5a2b",
|
|
1775
|
+
hazard: "#cc4444",
|
|
1776
|
+
goal: "#4488cc"
|
|
1777
|
+
};
|
|
1778
|
+
function SideScene({
|
|
1779
|
+
player,
|
|
1780
|
+
platforms,
|
|
1781
|
+
worldHeight,
|
|
1782
|
+
ppu,
|
|
1783
|
+
playerSprite,
|
|
1784
|
+
tileSprites,
|
|
1785
|
+
interpolate,
|
|
1786
|
+
features = [],
|
|
1787
|
+
events = [],
|
|
1788
|
+
assetManifest
|
|
1789
|
+
}) {
|
|
1790
|
+
const pw = player.width ?? 32;
|
|
1791
|
+
const ph = player.height ?? 48;
|
|
1792
|
+
const playerPos = [
|
|
1793
|
+
(player.x + pw / 2) / ppu,
|
|
1794
|
+
(worldHeight - player.y - ph) / ppu,
|
|
1795
|
+
0
|
|
1796
|
+
];
|
|
1797
|
+
const playerH = Math.max(ph / ppu, 0.5);
|
|
1798
|
+
return /* @__PURE__ */ jsxs("group", { children: [
|
|
1799
|
+
platforms.map((p, i) => {
|
|
1800
|
+
const platformType = p.type ?? "platform";
|
|
1801
|
+
const model = tileSprites?.[platformType]?.url;
|
|
1802
|
+
const topY = (worldHeight - p.y) / ppu;
|
|
1803
|
+
if (model) {
|
|
1804
|
+
const cells = Math.max(1, Math.round(p.width / ppu));
|
|
1805
|
+
const x0 = p.x / ppu;
|
|
1806
|
+
return /* @__PURE__ */ jsx("group", { children: Array.from({ length: cells }, (_, c) => /* @__PURE__ */ jsx("group", { position: [x0 + c + 0.5, topY - 0.475, 0], children: /* @__PURE__ */ jsx(ModelLoader, { url: model, scale: 0.95, fallbackGeometry: "box", castShadow: true, receiveShadow: true }) }, c)) }, `plat-${i}`);
|
|
1807
|
+
}
|
|
1808
|
+
return /* @__PURE__ */ jsxs(
|
|
1809
|
+
"mesh",
|
|
1810
|
+
{
|
|
1811
|
+
position: [(p.x + p.width / 2) / ppu, topY - p.height / 2 / ppu, 0],
|
|
1812
|
+
children: [
|
|
1813
|
+
/* @__PURE__ */ jsx("boxGeometry", { args: [p.width / ppu, p.height / ppu, 1] }),
|
|
1814
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color: SIDE_PLATFORM_COLORS[platformType] ?? "#888888" })
|
|
1815
|
+
]
|
|
1816
|
+
},
|
|
1817
|
+
`plat-${i}`
|
|
1818
|
+
);
|
|
1819
|
+
}),
|
|
1820
|
+
features.map((feature, i) => {
|
|
1821
|
+
const model = feature.assetUrl ?? assetManifest?.features?.[feature.type];
|
|
1822
|
+
const fx = feature.x / ppu;
|
|
1823
|
+
const fy = (worldHeight - feature.y) / ppu;
|
|
1824
|
+
if (!model?.url) return null;
|
|
1825
|
+
return /* @__PURE__ */ jsx("group", { position: [fx, fy, 0], children: /* @__PURE__ */ jsx(ModelLoader, { url: model.url, scale: 0.6, tint: feature.color, fallbackGeometry: "sphere", castShadow: true }) }, feature.id ?? `sfeat-${i}`);
|
|
1826
|
+
}),
|
|
1827
|
+
events.map((ev, i) => /* @__PURE__ */ jsx(
|
|
1828
|
+
EventMarker,
|
|
1829
|
+
{
|
|
1830
|
+
event: ev,
|
|
1831
|
+
position: [ev.x / ppu, (worldHeight - (ev.y ?? 0)) / ppu + 0.8, 0.2]
|
|
1832
|
+
},
|
|
1833
|
+
ev.id ?? `sev-${i}`
|
|
1834
|
+
)),
|
|
1835
|
+
/* @__PURE__ */ jsx(LerpedGroup, { target: playerPos, enabled: interpolate, children: playerSprite?.url ? /* @__PURE__ */ jsx("group", { position: [0, playerH / 2, 0], children: /* @__PURE__ */ jsx(
|
|
1836
|
+
ModelLoader,
|
|
1837
|
+
{
|
|
1838
|
+
url: playerSprite.url,
|
|
1839
|
+
scale: playerH,
|
|
1840
|
+
rotation: [0, player.facingRight ? 90 : -90, 0],
|
|
1841
|
+
animation: player.animation ?? "idle",
|
|
1842
|
+
fallbackGeometry: "box",
|
|
1843
|
+
castShadow: true
|
|
1844
|
+
}
|
|
1845
|
+
) }) : /* @__PURE__ */ jsxs("mesh", { position: [0, playerH / 2, 0], children: [
|
|
1846
|
+
/* @__PURE__ */ jsx("capsuleGeometry", { args: [playerH * 0.25, playerH * 0.5, 4, 8] }),
|
|
1847
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color: "#4488ff" })
|
|
1848
|
+
] }) })
|
|
1849
|
+
] });
|
|
1850
|
+
}
|
|
1851
|
+
var UNIT_BASE_MODEL_SCALE = 0.5;
|
|
1852
|
+
var UNIT_BASE_BILLBOARD_HEIGHT = 1.2;
|
|
1853
|
+
var UNIT_BASE_PRIMITIVE_RADIUS = 0.3;
|
|
1854
|
+
var EVENT_COLORS = {
|
|
1855
|
+
hit: "#ff5544",
|
|
1856
|
+
damage: "#ff5544",
|
|
1857
|
+
attack: "#ff8844",
|
|
1858
|
+
heal: "#44dd66",
|
|
1859
|
+
pickup: "#ffcc33",
|
|
1860
|
+
score: "#ffcc33",
|
|
1861
|
+
death: "#aa3333",
|
|
1862
|
+
win: "#66ff88",
|
|
1863
|
+
lose: "#ff6666"
|
|
1864
|
+
};
|
|
1865
|
+
function EventMarker({
|
|
1866
|
+
event,
|
|
1867
|
+
position
|
|
1868
|
+
}) {
|
|
1869
|
+
return /* @__PURE__ */ jsx(Billboard, { position, children: /* @__PURE__ */ jsx(
|
|
1870
|
+
Text,
|
|
1871
|
+
{
|
|
1872
|
+
fontSize: 0.32,
|
|
1873
|
+
color: EVENT_COLORS[event.type] ?? "#ffffff",
|
|
1874
|
+
outlineWidth: 0.02,
|
|
1875
|
+
outlineColor: "#000000",
|
|
1876
|
+
anchorX: "center",
|
|
1877
|
+
anchorY: "middle",
|
|
1878
|
+
children: event.message ?? event.type
|
|
1879
|
+
}
|
|
1880
|
+
) });
|
|
1881
|
+
}
|
|
1882
|
+
function DefaultTile({
|
|
1883
|
+
tile,
|
|
1884
|
+
position,
|
|
1885
|
+
model,
|
|
1886
|
+
isSelected,
|
|
1887
|
+
isHovered,
|
|
1888
|
+
isValidMove,
|
|
1889
|
+
isAttackTarget,
|
|
1890
|
+
onTileClick,
|
|
1891
|
+
onTileHover
|
|
1892
|
+
}) {
|
|
1893
|
+
let color = 8421504;
|
|
1894
|
+
if (tile.type === "water") color = 4491468;
|
|
1895
|
+
else if (tile.type === "grass") color = 4500036;
|
|
1896
|
+
else if (tile.type === "sand") color = 14535816;
|
|
1897
|
+
else if (tile.type === "rock") color = 8947848;
|
|
1898
|
+
else if (tile.type === "snow") color = 15658734;
|
|
1899
|
+
let emissive = 0;
|
|
1900
|
+
if (isSelected) emissive = 4473924;
|
|
1901
|
+
else if (isAttackTarget) emissive = 4456448;
|
|
1902
|
+
else if (isValidMove) emissive = 17408;
|
|
1903
|
+
else if (isHovered) emissive = 2236962;
|
|
1904
|
+
if (model?.url) {
|
|
1905
|
+
return /* @__PURE__ */ jsx(
|
|
1906
|
+
"group",
|
|
1907
|
+
{
|
|
1908
|
+
position,
|
|
1909
|
+
onClick: (e) => onTileClick(tile, e),
|
|
1910
|
+
onPointerEnter: (e) => onTileHover(tile, e),
|
|
1911
|
+
onPointerLeave: (e) => onTileHover(null, e),
|
|
1912
|
+
userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
|
|
1913
|
+
children: /* @__PURE__ */ jsx(
|
|
1914
|
+
ModelLoader,
|
|
1915
|
+
{
|
|
1916
|
+
url: model.url,
|
|
1917
|
+
scale: 0.95,
|
|
1918
|
+
rotation: [0, tile.rotation ?? 0, 0],
|
|
1919
|
+
fallbackGeometry: "box",
|
|
1920
|
+
castShadow: true,
|
|
1921
|
+
receiveShadow: true
|
|
1922
|
+
}
|
|
1923
|
+
)
|
|
1924
|
+
}
|
|
1925
|
+
);
|
|
1926
|
+
}
|
|
1927
|
+
return /* @__PURE__ */ jsxs(
|
|
1928
|
+
"mesh",
|
|
1929
|
+
{
|
|
1930
|
+
position,
|
|
1931
|
+
onClick: (e) => onTileClick(tile, e),
|
|
1932
|
+
onPointerEnter: (e) => onTileHover(tile, e),
|
|
1933
|
+
onPointerLeave: (e) => onTileHover(null, e),
|
|
1934
|
+
userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
|
|
1935
|
+
children: [
|
|
1936
|
+
/* @__PURE__ */ jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
|
|
1937
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color, emissive })
|
|
1938
|
+
]
|
|
1939
|
+
}
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
1942
|
+
function DefaultUnit({
|
|
1943
|
+
unit,
|
|
1944
|
+
position,
|
|
1945
|
+
model,
|
|
1946
|
+
isSelected,
|
|
1947
|
+
unitScale,
|
|
1948
|
+
cellSize,
|
|
1949
|
+
resolveUnitFrame,
|
|
1950
|
+
onUnitClick
|
|
1951
|
+
}) {
|
|
1952
|
+
const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
|
|
1953
|
+
const hasAtlas = unitAtlasUrl(unit) !== null;
|
|
1954
|
+
const initialFrame = hasAtlas ? resolveUnitFrame(unit.id) : null;
|
|
1955
|
+
const modelScale = UNIT_BASE_MODEL_SCALE * unitScale * cellSize;
|
|
1956
|
+
const billboardHeight = UNIT_BASE_BILLBOARD_HEIGHT * unitScale * cellSize;
|
|
1957
|
+
const primitiveRadius = UNIT_BASE_PRIMITIVE_RADIUS * unitScale * cellSize;
|
|
1958
|
+
return /* @__PURE__ */ jsxs(
|
|
1959
|
+
"group",
|
|
1960
|
+
{
|
|
1961
|
+
position,
|
|
1962
|
+
onClick: (e) => onUnitClick(unit, e),
|
|
1963
|
+
userData: { type: "unit", unitId: unit.id },
|
|
1964
|
+
children: [
|
|
1965
|
+
isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
|
|
1966
|
+
/* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
|
|
1967
|
+
/* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
|
|
1968
|
+
] }),
|
|
1969
|
+
hasAtlas && initialFrame ? (
|
|
1970
|
+
/* Animated sprite-sheet billboard — single cropped frame, by state */
|
|
1971
|
+
/* @__PURE__ */ jsx(Billboard, { children: /* @__PURE__ */ jsx(
|
|
1972
|
+
UnitSpriteBillboard,
|
|
1973
|
+
{
|
|
1974
|
+
sheetUrl: initialFrame.sheetUrl,
|
|
1975
|
+
resolveFrame: () => resolveUnitFrame(unit.id),
|
|
1976
|
+
height: billboardHeight
|
|
1977
|
+
}
|
|
1978
|
+
) })
|
|
1979
|
+
) : model?.url ? (
|
|
1980
|
+
/* GLB unit model — LOLO's `animation` field drives the named clip */
|
|
1981
|
+
/* @__PURE__ */ jsx(
|
|
1982
|
+
ModelLoader,
|
|
1983
|
+
{
|
|
1984
|
+
url: model.url,
|
|
1985
|
+
scale: modelScale,
|
|
1986
|
+
animation: unit.animation ?? "idle",
|
|
1987
|
+
fallbackGeometry: "box",
|
|
1988
|
+
castShadow: true
|
|
1989
|
+
}
|
|
1990
|
+
)
|
|
1991
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1992
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, primitiveRadius, 0], children: [
|
|
1993
|
+
/* @__PURE__ */ jsx("cylinderGeometry", { args: [primitiveRadius, primitiveRadius, primitiveRadius * 0.33, 8] }),
|
|
1994
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color })
|
|
1995
|
+
] }),
|
|
1996
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, primitiveRadius * 2, 0], children: [
|
|
1997
|
+
/* @__PURE__ */ jsx("capsuleGeometry", { args: [primitiveRadius * 0.67, primitiveRadius * 1.33, 4, 8] }),
|
|
1998
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color })
|
|
1999
|
+
] }),
|
|
2000
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, primitiveRadius * 3, 0], children: [
|
|
2001
|
+
/* @__PURE__ */ jsx("sphereGeometry", { args: [primitiveRadius * 0.4, 8, 8] }),
|
|
2002
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color })
|
|
2003
|
+
] })
|
|
2004
|
+
] }),
|
|
2005
|
+
unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxs("group", { position: [0, billboardHeight, 0], children: [
|
|
2006
|
+
/* @__PURE__ */ jsxs("mesh", { position: [-0.25, 0, 0], children: [
|
|
2007
|
+
/* @__PURE__ */ jsx("planeGeometry", { args: [0.5, 0.05] }),
|
|
2008
|
+
/* @__PURE__ */ jsx("meshBasicMaterial", { color: 3355443 })
|
|
2009
|
+
] }),
|
|
2010
|
+
/* @__PURE__ */ jsxs(
|
|
2011
|
+
"mesh",
|
|
2012
|
+
{
|
|
2013
|
+
position: [
|
|
2014
|
+
-0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
|
|
2015
|
+
0,
|
|
2016
|
+
0.01
|
|
2017
|
+
],
|
|
2018
|
+
children: [
|
|
2019
|
+
/* @__PURE__ */ jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
|
|
2020
|
+
/* @__PURE__ */ jsx(
|
|
2021
|
+
"meshBasicMaterial",
|
|
2022
|
+
{
|
|
2023
|
+
color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
|
|
2024
|
+
}
|
|
2025
|
+
)
|
|
2026
|
+
]
|
|
2027
|
+
}
|
|
2028
|
+
)
|
|
2029
|
+
] })
|
|
2030
|
+
]
|
|
2031
|
+
}
|
|
2032
|
+
);
|
|
2033
|
+
}
|
|
2034
|
+
function DefaultFeature({
|
|
2035
|
+
feature,
|
|
2036
|
+
position,
|
|
2037
|
+
model,
|
|
2038
|
+
onFeatureClick
|
|
2039
|
+
}) {
|
|
2040
|
+
if (model?.url) {
|
|
2041
|
+
return /* @__PURE__ */ jsx(
|
|
2042
|
+
ModelLoader,
|
|
2043
|
+
{
|
|
2044
|
+
url: model.url,
|
|
2045
|
+
position,
|
|
2046
|
+
scale: 0.5,
|
|
2047
|
+
rotation: [0, feature.rotation ?? 0, 0],
|
|
2048
|
+
tint: feature.color,
|
|
2049
|
+
onClick: () => onFeatureClick(feature, null),
|
|
2050
|
+
fallbackGeometry: "box"
|
|
2051
|
+
}
|
|
2052
|
+
);
|
|
2053
|
+
}
|
|
2054
|
+
if (feature.type === "tree") {
|
|
2055
|
+
return /* @__PURE__ */ jsxs(
|
|
2056
|
+
"group",
|
|
2057
|
+
{
|
|
2058
|
+
position,
|
|
2059
|
+
onClick: (e) => onFeatureClick(feature, e),
|
|
2060
|
+
userData: { type: "feature", featureId: feature.id },
|
|
2061
|
+
children: [
|
|
2062
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, 0.4, 0], children: [
|
|
2063
|
+
/* @__PURE__ */ jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
|
|
2064
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color: 9127187 })
|
|
2065
|
+
] }),
|
|
2066
|
+
/* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
|
|
2067
|
+
/* @__PURE__ */ jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
|
|
2068
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color: 2263842 })
|
|
2069
|
+
] })
|
|
2070
|
+
]
|
|
2071
|
+
}
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
2074
|
+
if (feature.type === "rock") {
|
|
2075
|
+
return /* @__PURE__ */ jsxs(
|
|
2076
|
+
"mesh",
|
|
2077
|
+
{
|
|
2078
|
+
position: [position[0], position[1] + 0.3, position[2]],
|
|
2079
|
+
onClick: (e) => onFeatureClick(feature, e),
|
|
2080
|
+
userData: { type: "feature", featureId: feature.id },
|
|
2081
|
+
children: [
|
|
2082
|
+
/* @__PURE__ */ jsx("dodecahedronGeometry", { args: [0.3, 0] }),
|
|
2083
|
+
/* @__PURE__ */ jsx("meshStandardMaterial", { color: 8421504 })
|
|
2084
|
+
]
|
|
2085
|
+
}
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
return null;
|
|
2089
|
+
}
|
|
1698
2090
|
var GameCanvas3D = forwardRef(
|
|
1699
2091
|
({
|
|
1700
2092
|
tiles = [],
|
|
@@ -1738,12 +2130,47 @@ var GameCanvas3D = forwardRef(
|
|
|
1738
2130
|
selectedTileIds = [],
|
|
1739
2131
|
selectedUnitId = null,
|
|
1740
2132
|
unitScale = 1,
|
|
2133
|
+
keyMap,
|
|
2134
|
+
keyUpMap,
|
|
2135
|
+
player,
|
|
2136
|
+
platforms = [],
|
|
2137
|
+
worldHeight = 400,
|
|
2138
|
+
pixelsPerUnit = 32,
|
|
2139
|
+
playerSprite,
|
|
2140
|
+
tileSprites,
|
|
2141
|
+
assetManifest,
|
|
2142
|
+
interpolateUnits = false,
|
|
1741
2143
|
children
|
|
1742
2144
|
}, ref) => {
|
|
1743
2145
|
const containerRef = useRef(null);
|
|
1744
2146
|
const controlsRef = useRef(null);
|
|
1745
2147
|
const [hoveredTile, setHoveredTile] = useState(null);
|
|
1746
2148
|
const [internalError, setInternalError] = useState(null);
|
|
2149
|
+
const eventBus = useEventBus();
|
|
2150
|
+
const keysRef = useRef(/* @__PURE__ */ new Set());
|
|
2151
|
+
useEffect(() => {
|
|
2152
|
+
if (!keyMap && !keyUpMap) return;
|
|
2153
|
+
const down = (e) => {
|
|
2154
|
+
if (keysRef.current.has(e.code)) return;
|
|
2155
|
+
keysRef.current.add(e.code);
|
|
2156
|
+
const ev = keyMap?.[e.code];
|
|
2157
|
+
if (ev) {
|
|
2158
|
+
eventBus.emit(`UI:${ev}`, {});
|
|
2159
|
+
e.preventDefault();
|
|
2160
|
+
}
|
|
2161
|
+
};
|
|
2162
|
+
const up = (e) => {
|
|
2163
|
+
keysRef.current.delete(e.code);
|
|
2164
|
+
const ev = keyUpMap?.[e.code];
|
|
2165
|
+
if (ev) eventBus.emit(`UI:${ev}`, {});
|
|
2166
|
+
};
|
|
2167
|
+
window.addEventListener("keydown", down);
|
|
2168
|
+
window.addEventListener("keyup", up);
|
|
2169
|
+
return () => {
|
|
2170
|
+
window.removeEventListener("keydown", down);
|
|
2171
|
+
window.removeEventListener("keyup", up);
|
|
2172
|
+
};
|
|
2173
|
+
}, [keyMap, keyUpMap, eventBus]);
|
|
1747
2174
|
useEffect(() => {
|
|
1748
2175
|
const el = containerRef.current;
|
|
1749
2176
|
if (!el) return;
|
|
@@ -1913,6 +2340,11 @@ var GameCanvas3D = forwardRef(
|
|
|
1913
2340
|
position: [cx, d * 2, cz],
|
|
1914
2341
|
fov: 45
|
|
1915
2342
|
};
|
|
2343
|
+
case "follow":
|
|
2344
|
+
return {
|
|
2345
|
+
position: [cx, d * 0.5, cz + d],
|
|
2346
|
+
fov: 45
|
|
2347
|
+
};
|
|
1916
2348
|
case "perspective":
|
|
1917
2349
|
default:
|
|
1918
2350
|
return {
|
|
@@ -1921,212 +2353,26 @@ var GameCanvas3D = forwardRef(
|
|
|
1921
2353
|
};
|
|
1922
2354
|
}
|
|
1923
2355
|
}, [cameraMode, gridBounds]);
|
|
1924
|
-
const
|
|
1925
|
-
(
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
else if (isHovered) emissive = 2236962;
|
|
1945
|
-
if (tile.modelUrl) {
|
|
1946
|
-
return /* @__PURE__ */ jsx(
|
|
1947
|
-
"group",
|
|
1948
|
-
{
|
|
1949
|
-
position,
|
|
1950
|
-
onClick: (e) => handleTileClick(tile, e),
|
|
1951
|
-
onPointerEnter: (e) => handleTileHover(tile, e),
|
|
1952
|
-
onPointerLeave: (e) => handleTileHover(null, e),
|
|
1953
|
-
userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
|
|
1954
|
-
children: /* @__PURE__ */ jsx(
|
|
1955
|
-
ModelLoader,
|
|
1956
|
-
{
|
|
1957
|
-
url: tile.modelUrl.url,
|
|
1958
|
-
scale: 0.95,
|
|
1959
|
-
fallbackGeometry: "box",
|
|
1960
|
-
castShadow: true,
|
|
1961
|
-
receiveShadow: true
|
|
1962
|
-
}
|
|
1963
|
-
)
|
|
1964
|
-
}
|
|
1965
|
-
);
|
|
1966
|
-
}
|
|
1967
|
-
return /* @__PURE__ */ jsxs(
|
|
1968
|
-
"mesh",
|
|
1969
|
-
{
|
|
1970
|
-
position,
|
|
1971
|
-
onClick: (e) => handleTileClick(tile, e),
|
|
1972
|
-
onPointerEnter: (e) => handleTileHover(tile, e),
|
|
1973
|
-
onPointerLeave: (e) => handleTileHover(null, e),
|
|
1974
|
-
userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
|
|
1975
|
-
children: [
|
|
1976
|
-
/* @__PURE__ */ jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
|
|
1977
|
-
/* @__PURE__ */ jsx("meshStandardMaterial", { color, emissive })
|
|
1978
|
-
]
|
|
1979
|
-
}
|
|
1980
|
-
);
|
|
1981
|
-
},
|
|
1982
|
-
[selectedTileIds, hoveredTile, validMoves, attackTargets, handleTileClick, handleTileHover]
|
|
1983
|
-
);
|
|
1984
|
-
const UNIT_BASE_MODEL_SCALE = 0.5;
|
|
1985
|
-
const UNIT_BASE_BILLBOARD_HEIGHT = 1.2;
|
|
1986
|
-
const UNIT_BASE_PRIMITIVE_RADIUS = 0.3;
|
|
1987
|
-
const DefaultUnitRenderer = useCallback(
|
|
1988
|
-
({ unit, position }) => {
|
|
1989
|
-
const isSelected = selectedUnitId === unit.id;
|
|
1990
|
-
const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
|
|
1991
|
-
const hasAtlas = unitAtlasUrl(unit) !== null;
|
|
1992
|
-
const initialFrame = hasAtlas ? resolveUnitFrame(unit.id) : null;
|
|
1993
|
-
const modelScale = UNIT_BASE_MODEL_SCALE * unitScale * gridConfig.cellSize;
|
|
1994
|
-
const billboardHeight = UNIT_BASE_BILLBOARD_HEIGHT * unitScale * gridConfig.cellSize;
|
|
1995
|
-
const primitiveRadius = UNIT_BASE_PRIMITIVE_RADIUS * unitScale * gridConfig.cellSize;
|
|
1996
|
-
return /* @__PURE__ */ jsxs(
|
|
1997
|
-
"group",
|
|
1998
|
-
{
|
|
1999
|
-
position,
|
|
2000
|
-
onClick: (e) => handleUnitClick(unit, e),
|
|
2001
|
-
userData: { type: "unit", unitId: unit.id },
|
|
2002
|
-
children: [
|
|
2003
|
-
isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
|
|
2004
|
-
/* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
|
|
2005
|
-
/* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
|
|
2006
|
-
] }),
|
|
2007
|
-
hasAtlas && initialFrame ? (
|
|
2008
|
-
/* Animated sprite-sheet billboard — single cropped frame, by state */
|
|
2009
|
-
/* @__PURE__ */ jsx(Billboard, { children: /* @__PURE__ */ jsx(
|
|
2010
|
-
UnitSpriteBillboard,
|
|
2011
|
-
{
|
|
2012
|
-
sheetUrl: initialFrame.sheetUrl,
|
|
2013
|
-
resolveFrame: () => resolveUnitFrame(unit.id),
|
|
2014
|
-
height: billboardHeight
|
|
2015
|
-
}
|
|
2016
|
-
) })
|
|
2017
|
-
) : unit.modelUrl ? (
|
|
2018
|
-
/* GLB unit model (box fallback while loading / on error) */
|
|
2019
|
-
/* @__PURE__ */ jsx(
|
|
2020
|
-
ModelLoader,
|
|
2021
|
-
{
|
|
2022
|
-
url: unit.modelUrl.url,
|
|
2023
|
-
scale: modelScale,
|
|
2024
|
-
fallbackGeometry: "box",
|
|
2025
|
-
castShadow: true
|
|
2026
|
-
}
|
|
2027
|
-
)
|
|
2028
|
-
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2029
|
-
/* @__PURE__ */ jsxs("mesh", { position: [0, primitiveRadius, 0], children: [
|
|
2030
|
-
/* @__PURE__ */ jsx("cylinderGeometry", { args: [primitiveRadius, primitiveRadius, primitiveRadius * 0.33, 8] }),
|
|
2031
|
-
/* @__PURE__ */ jsx("meshStandardMaterial", { color })
|
|
2032
|
-
] }),
|
|
2033
|
-
/* @__PURE__ */ jsxs("mesh", { position: [0, primitiveRadius * 2, 0], children: [
|
|
2034
|
-
/* @__PURE__ */ jsx("capsuleGeometry", { args: [primitiveRadius * 0.67, primitiveRadius * 1.33, 4, 8] }),
|
|
2035
|
-
/* @__PURE__ */ jsx("meshStandardMaterial", { color })
|
|
2036
|
-
] }),
|
|
2037
|
-
/* @__PURE__ */ jsxs("mesh", { position: [0, primitiveRadius * 3, 0], children: [
|
|
2038
|
-
/* @__PURE__ */ jsx("sphereGeometry", { args: [primitiveRadius * 0.4, 8, 8] }),
|
|
2039
|
-
/* @__PURE__ */ jsx("meshStandardMaterial", { color })
|
|
2040
|
-
] })
|
|
2041
|
-
] }),
|
|
2042
|
-
unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxs("group", { position: [0, billboardHeight, 0], children: [
|
|
2043
|
-
/* @__PURE__ */ jsxs("mesh", { position: [-0.25, 0, 0], children: [
|
|
2044
|
-
/* @__PURE__ */ jsx("planeGeometry", { args: [0.5, 0.05] }),
|
|
2045
|
-
/* @__PURE__ */ jsx("meshBasicMaterial", { color: 3355443 })
|
|
2046
|
-
] }),
|
|
2047
|
-
/* @__PURE__ */ jsxs(
|
|
2048
|
-
"mesh",
|
|
2049
|
-
{
|
|
2050
|
-
position: [
|
|
2051
|
-
-0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
|
|
2052
|
-
0,
|
|
2053
|
-
0.01
|
|
2054
|
-
],
|
|
2055
|
-
children: [
|
|
2056
|
-
/* @__PURE__ */ jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
|
|
2057
|
-
/* @__PURE__ */ jsx(
|
|
2058
|
-
"meshBasicMaterial",
|
|
2059
|
-
{
|
|
2060
|
-
color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
|
|
2061
|
-
}
|
|
2062
|
-
)
|
|
2063
|
-
]
|
|
2064
|
-
}
|
|
2065
|
-
)
|
|
2066
|
-
] })
|
|
2067
|
-
]
|
|
2068
|
-
}
|
|
2069
|
-
);
|
|
2070
|
-
},
|
|
2071
|
-
[selectedUnitId, handleUnitClick, resolveUnitFrame, unitScale, gridConfig]
|
|
2072
|
-
);
|
|
2073
|
-
const DefaultFeatureRenderer = useCallback(
|
|
2074
|
-
({
|
|
2075
|
-
feature,
|
|
2076
|
-
position
|
|
2077
|
-
}) => {
|
|
2078
|
-
if (feature.assetUrl) {
|
|
2079
|
-
return /* @__PURE__ */ jsx(
|
|
2080
|
-
ModelLoader,
|
|
2081
|
-
{
|
|
2082
|
-
url: feature.assetUrl.url,
|
|
2083
|
-
position,
|
|
2084
|
-
scale: 0.5,
|
|
2085
|
-
rotation: [0, feature.rotation ?? 0, 0],
|
|
2086
|
-
onClick: () => handleFeatureClick(feature, null),
|
|
2087
|
-
fallbackGeometry: "box"
|
|
2088
|
-
},
|
|
2089
|
-
feature.id
|
|
2090
|
-
);
|
|
2091
|
-
}
|
|
2092
|
-
if (feature.type === "tree") {
|
|
2093
|
-
return /* @__PURE__ */ jsxs(
|
|
2094
|
-
"group",
|
|
2095
|
-
{
|
|
2096
|
-
position,
|
|
2097
|
-
onClick: (e) => handleFeatureClick(feature, e),
|
|
2098
|
-
userData: { type: "feature", featureId: feature.id },
|
|
2099
|
-
children: [
|
|
2100
|
-
/* @__PURE__ */ jsxs("mesh", { position: [0, 0.4, 0], children: [
|
|
2101
|
-
/* @__PURE__ */ jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
|
|
2102
|
-
/* @__PURE__ */ jsx("meshStandardMaterial", { color: 9127187 })
|
|
2103
|
-
] }),
|
|
2104
|
-
/* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
|
|
2105
|
-
/* @__PURE__ */ jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
|
|
2106
|
-
/* @__PURE__ */ jsx("meshStandardMaterial", { color: 2263842 })
|
|
2107
|
-
] })
|
|
2108
|
-
]
|
|
2109
|
-
}
|
|
2110
|
-
);
|
|
2111
|
-
}
|
|
2112
|
-
if (feature.type === "rock") {
|
|
2113
|
-
return /* @__PURE__ */ jsxs(
|
|
2114
|
-
"mesh",
|
|
2115
|
-
{
|
|
2116
|
-
position: [position[0], position[1] + 0.3, position[2]],
|
|
2117
|
-
onClick: (e) => handleFeatureClick(feature, e),
|
|
2118
|
-
userData: { type: "feature", featureId: feature.id },
|
|
2119
|
-
children: [
|
|
2120
|
-
/* @__PURE__ */ jsx("dodecahedronGeometry", { args: [0.3, 0] }),
|
|
2121
|
-
/* @__PURE__ */ jsx("meshStandardMaterial", { color: 8421504 })
|
|
2122
|
-
]
|
|
2123
|
-
}
|
|
2124
|
-
);
|
|
2125
|
-
}
|
|
2126
|
-
return null;
|
|
2127
|
-
},
|
|
2128
|
-
[handleFeatureClick]
|
|
2129
|
-
);
|
|
2356
|
+
const followTarget = useMemo(() => {
|
|
2357
|
+
if (player) {
|
|
2358
|
+
return [
|
|
2359
|
+
(player.x + (player.width ?? 32) / 2) / pixelsPerUnit,
|
|
2360
|
+
(worldHeight - player.y - (player.height ?? 48) / 2) / pixelsPerUnit,
|
|
2361
|
+
0
|
|
2362
|
+
];
|
|
2363
|
+
}
|
|
2364
|
+
const selected = units.find((u) => u.id === selectedUnitId);
|
|
2365
|
+
if (selected) {
|
|
2366
|
+
const sx = selected.x ?? selected.position?.x ?? 0;
|
|
2367
|
+
const sz = selected.z ?? selected.y ?? selected.position?.y ?? 0;
|
|
2368
|
+
return [
|
|
2369
|
+
(sx - gridBounds.minX) * gridConfig.cellSize,
|
|
2370
|
+
0,
|
|
2371
|
+
(sz - gridBounds.minZ) * gridConfig.cellSize
|
|
2372
|
+
];
|
|
2373
|
+
}
|
|
2374
|
+
return cameraTarget;
|
|
2375
|
+
}, [player, pixelsPerUnit, worldHeight, units, selectedUnitId, gridBounds, gridConfig, cameraTarget]);
|
|
2130
2376
|
if (externalLoading || assetsLoading && preloadAssets.length > 0) {
|
|
2131
2377
|
return /* @__PURE__ */ jsx(
|
|
2132
2378
|
Canvas3DLoadingState,
|
|
@@ -2179,6 +2425,13 @@ var GameCanvas3D = forwardRef(
|
|
|
2179
2425
|
},
|
|
2180
2426
|
children: [
|
|
2181
2427
|
/* @__PURE__ */ jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
|
|
2428
|
+
cameraMode === "follow" && /* @__PURE__ */ jsx(
|
|
2429
|
+
FollowCamera,
|
|
2430
|
+
{
|
|
2431
|
+
target: followTarget,
|
|
2432
|
+
offset: player ? [0, 2, 9] : [5, 7, 5]
|
|
2433
|
+
}
|
|
2434
|
+
),
|
|
2182
2435
|
/* @__PURE__ */ jsx("ambientLight", { intensity: 0.6 }),
|
|
2183
2436
|
/* @__PURE__ */ jsx(
|
|
2184
2437
|
"directionalLight",
|
|
@@ -2186,7 +2439,9 @@ var GameCanvas3D = forwardRef(
|
|
|
2186
2439
|
position: [10, 20, 10],
|
|
2187
2440
|
intensity: 0.8,
|
|
2188
2441
|
castShadow: shadows,
|
|
2189
|
-
"shadow-mapSize": [2048, 2048]
|
|
2442
|
+
"shadow-mapSize": [2048, 2048],
|
|
2443
|
+
"shadow-bias": -4e-4,
|
|
2444
|
+
"shadow-normalBias": 0.04
|
|
2190
2445
|
}
|
|
2191
2446
|
),
|
|
2192
2447
|
/* @__PURE__ */ jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
|
|
@@ -2212,15 +2467,49 @@ var GameCanvas3D = forwardRef(
|
|
|
2212
2467
|
fadeStrength: 1
|
|
2213
2468
|
}
|
|
2214
2469
|
),
|
|
2215
|
-
|
|
2470
|
+
player ? (
|
|
2471
|
+
/* Side-scroller mode — LOLO-owned pixel space mapped to world units */
|
|
2472
|
+
/* @__PURE__ */ jsx(
|
|
2473
|
+
SideScene,
|
|
2474
|
+
{
|
|
2475
|
+
player,
|
|
2476
|
+
platforms,
|
|
2477
|
+
worldHeight,
|
|
2478
|
+
ppu: pixelsPerUnit,
|
|
2479
|
+
playerSprite,
|
|
2480
|
+
tileSprites,
|
|
2481
|
+
interpolate: interpolateUnits,
|
|
2482
|
+
features,
|
|
2483
|
+
events,
|
|
2484
|
+
assetManifest
|
|
2485
|
+
}
|
|
2486
|
+
)
|
|
2487
|
+
) : /* @__PURE__ */ jsxs("group", { children: [
|
|
2216
2488
|
tiles.map((tile, index) => {
|
|
2217
2489
|
const position = gridToWorld2(
|
|
2218
2490
|
tile.x,
|
|
2219
2491
|
tile.z ?? tile.y ?? 0,
|
|
2220
2492
|
tile.elevation ?? 0
|
|
2221
2493
|
);
|
|
2222
|
-
const
|
|
2223
|
-
|
|
2494
|
+
const key = tile.id ?? `tile-${index}`;
|
|
2495
|
+
if (CustomTileRenderer) {
|
|
2496
|
+
return /* @__PURE__ */ jsx(CustomTileRenderer, { tile, position }, key);
|
|
2497
|
+
}
|
|
2498
|
+
return /* @__PURE__ */ jsx(
|
|
2499
|
+
DefaultTile,
|
|
2500
|
+
{
|
|
2501
|
+
tile,
|
|
2502
|
+
position,
|
|
2503
|
+
model: tile.modelUrl ?? assetManifest?.terrains?.[tile.terrain ?? tile.type ?? ""],
|
|
2504
|
+
isSelected: tile.id ? selectedTileIds.includes(tile.id) : false,
|
|
2505
|
+
isHovered: hoveredTile?.id === tile.id,
|
|
2506
|
+
isValidMove: validMoves.some((m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)),
|
|
2507
|
+
isAttackTarget: attackTargets.some((m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)),
|
|
2508
|
+
onTileClick: handleTileClick,
|
|
2509
|
+
onTileHover: handleTileHover
|
|
2510
|
+
},
|
|
2511
|
+
key
|
|
2512
|
+
);
|
|
2224
2513
|
}),
|
|
2225
2514
|
features.map((feature, index) => {
|
|
2226
2515
|
const position = gridToWorld2(
|
|
@@ -2228,17 +2517,51 @@ var GameCanvas3D = forwardRef(
|
|
|
2228
2517
|
feature.z ?? feature.y ?? 0,
|
|
2229
2518
|
(feature.elevation ?? 0) + 0.5
|
|
2230
2519
|
);
|
|
2231
|
-
const
|
|
2232
|
-
|
|
2520
|
+
const key = feature.id ?? `feature-${index}`;
|
|
2521
|
+
if (CustomFeatureRenderer) {
|
|
2522
|
+
return /* @__PURE__ */ jsx(CustomFeatureRenderer, { feature, position }, key);
|
|
2523
|
+
}
|
|
2524
|
+
return /* @__PURE__ */ jsx(
|
|
2525
|
+
DefaultFeature,
|
|
2526
|
+
{
|
|
2527
|
+
feature,
|
|
2528
|
+
position,
|
|
2529
|
+
model: feature.assetUrl ?? assetManifest?.features?.[feature.type],
|
|
2530
|
+
onFeatureClick: handleFeatureClick
|
|
2531
|
+
},
|
|
2532
|
+
key
|
|
2533
|
+
);
|
|
2233
2534
|
}),
|
|
2234
2535
|
units.map((unit) => {
|
|
2235
2536
|
const position = gridToWorld2(
|
|
2236
|
-
unit.x ?? 0,
|
|
2237
|
-
unit.z ?? unit.y ?? 0,
|
|
2537
|
+
unit.x ?? unit.position?.x ?? 0,
|
|
2538
|
+
unit.z ?? unit.y ?? unit.position?.y ?? 0,
|
|
2238
2539
|
(unit.elevation ?? 0) + 0.5
|
|
2239
2540
|
);
|
|
2240
|
-
|
|
2241
|
-
|
|
2541
|
+
return /* @__PURE__ */ jsx(LerpedGroup, { target: position, enabled: interpolateUnits, children: CustomUnitRenderer ? /* @__PURE__ */ jsx(CustomUnitRenderer, { unit, position: [0, 0, 0] }) : /* @__PURE__ */ jsx(
|
|
2542
|
+
DefaultUnit,
|
|
2543
|
+
{
|
|
2544
|
+
unit,
|
|
2545
|
+
position: [0, 0, 0],
|
|
2546
|
+
model: unit.modelUrl ?? assetManifest?.units?.[unit.unitType ?? ""],
|
|
2547
|
+
isSelected: selectedUnitId === unit.id,
|
|
2548
|
+
unitScale,
|
|
2549
|
+
cellSize: gridConfig.cellSize,
|
|
2550
|
+
resolveUnitFrame,
|
|
2551
|
+
onUnitClick: handleUnitClick
|
|
2552
|
+
}
|
|
2553
|
+
) }, unit.id);
|
|
2554
|
+
}),
|
|
2555
|
+
events.map((ev, i) => {
|
|
2556
|
+
const position = gridToWorld2(ev.x, ev.z ?? ev.y ?? 0, 0);
|
|
2557
|
+
return /* @__PURE__ */ jsx(
|
|
2558
|
+
EventMarker,
|
|
2559
|
+
{
|
|
2560
|
+
event: ev,
|
|
2561
|
+
position: [position[0], position[1] + 1.4, position[2]]
|
|
2562
|
+
},
|
|
2563
|
+
ev.id ?? `ev-${i}`
|
|
2564
|
+
);
|
|
2242
2565
|
})
|
|
2243
2566
|
] }),
|
|
2244
2567
|
children,
|
|
@@ -2246,6 +2569,7 @@ var GameCanvas3D = forwardRef(
|
|
|
2246
2569
|
OrbitControls,
|
|
2247
2570
|
{
|
|
2248
2571
|
ref: controlsRef,
|
|
2572
|
+
enabled: cameraMode !== "follow",
|
|
2249
2573
|
target: cameraTarget,
|
|
2250
2574
|
enableDamping: true,
|
|
2251
2575
|
dampingFactor: 0.05,
|
|
@@ -2278,9 +2602,6 @@ var GameCanvas3D = forwardRef(
|
|
|
2278
2602
|
}
|
|
2279
2603
|
);
|
|
2280
2604
|
GameCanvas3D.displayName = "GameCanvas3D";
|
|
2281
|
-
function Canvas3D(props) {
|
|
2282
|
-
return /* @__PURE__ */ jsx(GameCanvas3D, { ...props });
|
|
2283
|
-
}
|
|
2284
2605
|
var DEFAULT_FAMILY = "lucide";
|
|
2285
2606
|
var VALID_FAMILIES = [
|
|
2286
2607
|
"lucide",
|
|
@@ -2353,7 +2674,7 @@ function loadLib(key, importer) {
|
|
|
2353
2674
|
return p;
|
|
2354
2675
|
}
|
|
2355
2676
|
function lazyFamilyIcon(libKey, importer, pick, fallbackName, family) {
|
|
2356
|
-
const Lazy =
|
|
2677
|
+
const Lazy = React8__default.lazy(async () => {
|
|
2357
2678
|
const lib = await loadLib(libKey, importer);
|
|
2358
2679
|
const Comp = pick(lib);
|
|
2359
2680
|
if (!Comp) {
|
|
@@ -2363,7 +2684,7 @@ function lazyFamilyIcon(libKey, importer, pick, fallbackName, family) {
|
|
|
2363
2684
|
return { default: Comp };
|
|
2364
2685
|
});
|
|
2365
2686
|
const Wrapped = (props) => /* @__PURE__ */ jsx(
|
|
2366
|
-
|
|
2687
|
+
React8__default.Suspense,
|
|
2367
2688
|
{
|
|
2368
2689
|
fallback: /* @__PURE__ */ jsx(
|
|
2369
2690
|
"span",
|
|
@@ -3032,7 +3353,7 @@ var Icon = ({
|
|
|
3032
3353
|
const directIcon = typeof icon === "string" ? void 0 : icon;
|
|
3033
3354
|
const effectiveName = typeof icon === "string" ? icon : name;
|
|
3034
3355
|
const family = useIconFamily();
|
|
3035
|
-
const RenderedComponent =
|
|
3356
|
+
const RenderedComponent = React8__default.useMemo(() => {
|
|
3036
3357
|
if (directIcon) return null;
|
|
3037
3358
|
return effectiveName ? resolveIconForFamily(effectiveName, family) : null;
|
|
3038
3359
|
}, [directIcon, effectiveName, family]);
|
|
@@ -3080,6 +3401,143 @@ var Icon = ({
|
|
|
3080
3401
|
);
|
|
3081
3402
|
};
|
|
3082
3403
|
Icon.displayName = "Icon";
|
|
3404
|
+
|
|
3405
|
+
// lib/atlasSlice.ts
|
|
3406
|
+
var atlasCache = /* @__PURE__ */ new Map();
|
|
3407
|
+
function isTilesheet(a) {
|
|
3408
|
+
return typeof a.tileWidth === "number";
|
|
3409
|
+
}
|
|
3410
|
+
function getAtlas(url, onReady) {
|
|
3411
|
+
if (atlasCache.has(url)) return atlasCache.get(url) ?? void 0;
|
|
3412
|
+
atlasCache.set(url, void 0);
|
|
3413
|
+
fetch(url).then((r) => r.json()).then((json) => {
|
|
3414
|
+
atlasCache.set(url, json);
|
|
3415
|
+
onReady();
|
|
3416
|
+
}).catch(() => {
|
|
3417
|
+
atlasCache.set(url, null);
|
|
3418
|
+
});
|
|
3419
|
+
return void 0;
|
|
3420
|
+
}
|
|
3421
|
+
function subRectFor(atlas, sprite) {
|
|
3422
|
+
if (isTilesheet(atlas)) {
|
|
3423
|
+
let col;
|
|
3424
|
+
let row;
|
|
3425
|
+
if (sprite.includes(",")) {
|
|
3426
|
+
const [c, r] = sprite.split(",").map((n) => Number(n.trim()));
|
|
3427
|
+
col = c;
|
|
3428
|
+
row = r;
|
|
3429
|
+
} else {
|
|
3430
|
+
const i = Number(sprite);
|
|
3431
|
+
if (!Number.isFinite(i)) return null;
|
|
3432
|
+
col = i % atlas.columns;
|
|
3433
|
+
row = Math.floor(i / atlas.columns);
|
|
3434
|
+
}
|
|
3435
|
+
if (!Number.isFinite(col) || !Number.isFinite(row) || col < 0 || row < 0 || col >= atlas.columns || row >= atlas.rows) return null;
|
|
3436
|
+
const margin = atlas.margin ?? 0;
|
|
3437
|
+
const spacing = atlas.spacing ?? 0;
|
|
3438
|
+
return {
|
|
3439
|
+
sx: margin + col * (atlas.tileWidth + spacing),
|
|
3440
|
+
sy: margin + row * (atlas.tileHeight + spacing),
|
|
3441
|
+
sw: atlas.tileWidth,
|
|
3442
|
+
sh: atlas.tileHeight
|
|
3443
|
+
};
|
|
3444
|
+
}
|
|
3445
|
+
const st = atlas.subTextures[sprite];
|
|
3446
|
+
if (!st) return null;
|
|
3447
|
+
return { sx: st.x, sy: st.y, sw: st.width, sh: st.height };
|
|
3448
|
+
}
|
|
3449
|
+
function isAtlasAsset(asset) {
|
|
3450
|
+
return !!asset && typeof asset.atlas === "string" && typeof asset.sprite === "string";
|
|
3451
|
+
}
|
|
3452
|
+
var imageCache = /* @__PURE__ */ new Map();
|
|
3453
|
+
var imageWaiters = /* @__PURE__ */ new Map();
|
|
3454
|
+
function getSheetImage(url, onReady) {
|
|
3455
|
+
const cached = imageCache.get(url);
|
|
3456
|
+
if (cached) return cached;
|
|
3457
|
+
(imageWaiters.get(url) ?? imageWaiters.set(url, /* @__PURE__ */ new Set()).get(url)).add(onReady);
|
|
3458
|
+
if (!imageCache.has(url)) {
|
|
3459
|
+
imageCache.set(url, null);
|
|
3460
|
+
const img = new Image();
|
|
3461
|
+
img.crossOrigin = "anonymous";
|
|
3462
|
+
img.onload = () => {
|
|
3463
|
+
imageCache.set(url, img);
|
|
3464
|
+
imageWaiters.get(url)?.forEach((fn) => fn());
|
|
3465
|
+
imageWaiters.delete(url);
|
|
3466
|
+
};
|
|
3467
|
+
img.src = url;
|
|
3468
|
+
}
|
|
3469
|
+
return null;
|
|
3470
|
+
}
|
|
3471
|
+
function AtlasImage({
|
|
3472
|
+
asset,
|
|
3473
|
+
size,
|
|
3474
|
+
width,
|
|
3475
|
+
height,
|
|
3476
|
+
fill = false,
|
|
3477
|
+
fit = "contain",
|
|
3478
|
+
alt,
|
|
3479
|
+
className,
|
|
3480
|
+
style,
|
|
3481
|
+
"aria-hidden": ariaHidden
|
|
3482
|
+
}) {
|
|
3483
|
+
const [, bump] = React8.useReducer((x) => x + 1, 0);
|
|
3484
|
+
const canvasRef = React8.useRef(null);
|
|
3485
|
+
const sliced = isAtlasAsset(asset);
|
|
3486
|
+
const atlas = sliced ? getAtlas(asset.atlas, bump) : void 0;
|
|
3487
|
+
const img = sliced && asset?.url ? getSheetImage(asset.url, bump) : null;
|
|
3488
|
+
const rect = sliced && atlas ? subRectFor(atlas, asset.sprite) : null;
|
|
3489
|
+
React8.useEffect(() => {
|
|
3490
|
+
const canvas = canvasRef.current;
|
|
3491
|
+
if (!canvas || !img || !rect) return;
|
|
3492
|
+
canvas.width = rect.sw;
|
|
3493
|
+
canvas.height = rect.sh;
|
|
3494
|
+
const ctx = canvas.getContext("2d");
|
|
3495
|
+
if (!ctx) return;
|
|
3496
|
+
ctx.imageSmoothingEnabled = false;
|
|
3497
|
+
ctx.clearRect(0, 0, rect.sw, rect.sh);
|
|
3498
|
+
ctx.drawImage(img, rect.sx, rect.sy, rect.sw, rect.sh, 0, 0, rect.sw, rect.sh);
|
|
3499
|
+
}, [img, rect?.sx, rect?.sy, rect?.sw, rect?.sh]);
|
|
3500
|
+
const w = fill ? "100%" : width ?? size;
|
|
3501
|
+
const h = fill ? "100%" : height ?? size;
|
|
3502
|
+
const boxStyle = {
|
|
3503
|
+
...fill ? { position: "absolute", inset: 0 } : {},
|
|
3504
|
+
width: w,
|
|
3505
|
+
height: h,
|
|
3506
|
+
objectFit: fit,
|
|
3507
|
+
imageRendering: "pixelated",
|
|
3508
|
+
...style
|
|
3509
|
+
};
|
|
3510
|
+
if (!asset?.url) return null;
|
|
3511
|
+
if (sliced) {
|
|
3512
|
+
if (!rect || !img) {
|
|
3513
|
+
return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: cn("inline-block flex-shrink-0", className), style: { ...boxStyle, objectFit: void 0 } });
|
|
3514
|
+
}
|
|
3515
|
+
return /* @__PURE__ */ jsx(
|
|
3516
|
+
"canvas",
|
|
3517
|
+
{
|
|
3518
|
+
ref: canvasRef,
|
|
3519
|
+
role: ariaHidden ? void 0 : "img",
|
|
3520
|
+
"aria-hidden": ariaHidden,
|
|
3521
|
+
"aria-label": ariaHidden ? void 0 : alt ?? asset.name ?? asset.category ?? "",
|
|
3522
|
+
className: cn("flex-shrink-0", className),
|
|
3523
|
+
style: boxStyle
|
|
3524
|
+
}
|
|
3525
|
+
);
|
|
3526
|
+
}
|
|
3527
|
+
return /* @__PURE__ */ jsx(
|
|
3528
|
+
"img",
|
|
3529
|
+
{
|
|
3530
|
+
src: asset.url,
|
|
3531
|
+
alt: alt ?? asset.name ?? asset.category ?? "",
|
|
3532
|
+
"aria-hidden": ariaHidden,
|
|
3533
|
+
...typeof w === "number" ? { width: w } : {},
|
|
3534
|
+
...typeof h === "number" ? { height: h } : {},
|
|
3535
|
+
className: cn("flex-shrink-0", className),
|
|
3536
|
+
style: boxStyle
|
|
3537
|
+
}
|
|
3538
|
+
);
|
|
3539
|
+
}
|
|
3540
|
+
AtlasImage.displayName = "AtlasImage";
|
|
3083
3541
|
var variantStyles = {
|
|
3084
3542
|
primary: [
|
|
3085
3543
|
"bg-primary text-primary-foreground",
|
|
@@ -3152,7 +3610,7 @@ function resolveIconProp(value, sizeClass) {
|
|
|
3152
3610
|
const IconComp = value;
|
|
3153
3611
|
return /* @__PURE__ */ jsx(IconComp, { className: sizeClass });
|
|
3154
3612
|
}
|
|
3155
|
-
if (
|
|
3613
|
+
if (React8__default.isValidElement(value)) {
|
|
3156
3614
|
return value;
|
|
3157
3615
|
}
|
|
3158
3616
|
if (typeof value === "object" && value !== null && isIconLike(value)) {
|
|
@@ -3161,7 +3619,7 @@ function resolveIconProp(value, sizeClass) {
|
|
|
3161
3619
|
}
|
|
3162
3620
|
return value;
|
|
3163
3621
|
}
|
|
3164
|
-
var Button =
|
|
3622
|
+
var Button = React8__default.forwardRef(
|
|
3165
3623
|
({
|
|
3166
3624
|
className,
|
|
3167
3625
|
variant = "primary",
|
|
@@ -3185,7 +3643,7 @@ var Button = React6.forwardRef(
|
|
|
3185
3643
|
const leftIconValue = leftIcon || iconProp;
|
|
3186
3644
|
const rightIconValue = rightIcon || iconRightProp;
|
|
3187
3645
|
const px = size === "sm" ? 16 : size === "lg" ? 20 : 16;
|
|
3188
|
-
const resolvedLeftIcon = iconAsset?.url ? /* @__PURE__ */ jsx(
|
|
3646
|
+
const resolvedLeftIcon = iconAsset?.url ? /* @__PURE__ */ jsx(AtlasImage, { asset: iconAsset, size: px, alt: iconAsset.name ?? iconAsset.category ?? "", className: "flex-shrink-0" }) : resolveIconProp(leftIconValue, iconSizeStyles[size]);
|
|
3189
3647
|
const resolvedRightIcon = resolveIconProp(rightIconValue, iconSizeStyles[size]);
|
|
3190
3648
|
const handleClick = (e) => {
|
|
3191
3649
|
if (action) {
|
|
@@ -3309,7 +3767,7 @@ var Typography = ({
|
|
|
3309
3767
|
}) => {
|
|
3310
3768
|
const variant = variantProp ?? (level ? `h${level}` : "body1");
|
|
3311
3769
|
const Component2 = as || defaultElements[variant];
|
|
3312
|
-
return
|
|
3770
|
+
return React8__default.createElement(
|
|
3313
3771
|
Component2,
|
|
3314
3772
|
{
|
|
3315
3773
|
id,
|
|
@@ -3382,7 +3840,7 @@ var Stack = ({
|
|
|
3382
3840
|
};
|
|
3383
3841
|
const isHorizontal = direction === "horizontal";
|
|
3384
3842
|
const directionClass = responsive && isHorizontal ? reverse ? "flex-col-reverse md:flex-row-reverse" : "flex-col md:flex-row" : isHorizontal ? reverse ? "flex-row-reverse" : "flex-row" : reverse ? "flex-col-reverse" : "flex-col";
|
|
3385
|
-
return
|
|
3843
|
+
return React8__default.createElement(
|
|
3386
3844
|
Component2,
|
|
3387
3845
|
{
|
|
3388
3846
|
className: cn(
|
|
@@ -3757,7 +4215,7 @@ var positionStyles = {
|
|
|
3757
4215
|
fixed: "fixed",
|
|
3758
4216
|
sticky: "sticky"
|
|
3759
4217
|
};
|
|
3760
|
-
var Box =
|
|
4218
|
+
var Box = React8__default.forwardRef(
|
|
3761
4219
|
({
|
|
3762
4220
|
padding,
|
|
3763
4221
|
paddingX,
|
|
@@ -3822,7 +4280,7 @@ var Box = React6.forwardRef(
|
|
|
3822
4280
|
onPointerDown?.(e);
|
|
3823
4281
|
}, [hoverEvent, tapReveal, triggerProps, onPointerDown]);
|
|
3824
4282
|
const isClickable = action || onClick;
|
|
3825
|
-
return
|
|
4283
|
+
return React8__default.createElement(
|
|
3826
4284
|
Component2,
|
|
3827
4285
|
{
|
|
3828
4286
|
ref,
|
|
@@ -6610,7 +7068,7 @@ var Avl3DViewer = ({
|
|
|
6610
7068
|
const handleTraitClick = useCallback((name) => {
|
|
6611
7069
|
dispatch({ type: "ZOOM_INTO_TRAIT", trait: name, targetPosition: { x: 0, y: 0 } });
|
|
6612
7070
|
}, []);
|
|
6613
|
-
const [highlightedTrait, setHighlightedTrait] =
|
|
7071
|
+
const [highlightedTrait, setHighlightedTrait] = React8__default.useState(null);
|
|
6614
7072
|
const handleTransitionClick = useCallback((index) => {
|
|
6615
7073
|
dispatch({ type: "ZOOM_INTO_TRANSITION", transitionIndex: index, targetPosition: { x: 0, y: 0 } });
|
|
6616
7074
|
}, []);
|
|
@@ -6697,7 +7155,7 @@ var Avl3DViewer = ({
|
|
|
6697
7155
|
gap: "xs",
|
|
6698
7156
|
align: "center",
|
|
6699
7157
|
className: "absolute top-2 left-2 z-10 bg-surface/80 backdrop-blur rounded-md px-3 py-1.5",
|
|
6700
|
-
children: breadcrumbs.map((crumb, i) => /* @__PURE__ */ jsxs(
|
|
7158
|
+
children: breadcrumbs.map((crumb, i) => /* @__PURE__ */ jsxs(React8__default.Fragment, { children: [
|
|
6701
7159
|
i > 0 && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", className: "mx-1", children: "/" }),
|
|
6702
7160
|
i < breadcrumbs.length - 1 ? /* @__PURE__ */ jsx(
|
|
6703
7161
|
Box,
|
|
@@ -6973,4 +7431,4 @@ var SpatialHashGrid = class {
|
|
|
6973
7431
|
}
|
|
6974
7432
|
};
|
|
6975
7433
|
|
|
6976
|
-
export { AVL_3D_COLORS, AssetLoader, Avl3DApplicationScene, Avl3DContext, Avl3DEffects, Avl3DOrbitalScene, Avl3DTraitScene, Avl3DTransitionScene, Avl3DViewer, CAMERA_POSITIONS, Camera3D,
|
|
7434
|
+
export { AVL_3D_COLORS, AssetLoader, Avl3DApplicationScene, Avl3DContext, Avl3DEffects, Avl3DOrbitalScene, Avl3DTraitScene, Avl3DTransitionScene, Avl3DViewer, CAMERA_POSITIONS, Camera3D, Canvas3DErrorBoundary, Canvas3DLoadingState, GameBoard3D, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, Lighting3D, ModelLoader, Scene3D, SpatialHashGrid, arcCurve3D, assetLoader, calculateLODLevel, createGridHighlight, cullInstancedMesh, fibonacciSpherePositions, filterByFrustum, getCellsInRadius, getNeighbors, getVisibleIndices, goldenSpiralPositions, gridDistance, gridManhattanDistance, gridToWorld, isInBounds, isInFrustum, normalizeMouseCoordinates, orbitRingPositions, raycastToObjects, raycastToPlane, selfLoopCurve3D, treeLayout3D, updateInstanceLOD, useAssetLoader, useAvl3DConfig, useGameCanvas3DEvents, useRaycaster, useSceneGraph, useThree3 as useThree, worldToGrid };
|