@almadar/ui 5.49.1 → 5.51.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 +519 -215
- package/dist/avl/index.js +521 -217
- package/dist/components/game/molecules/three/index.cjs +251 -3
- package/dist/components/game/molecules/three/index.js +253 -5
- package/dist/components/game/molecules/useUnitSpriteAtlas.d.ts +30 -0
- package/dist/components/game/organisms/types/isometric.d.ts +4 -0
- package/dist/components/game/organisms/types/spriteAnimation.d.ts +24 -0
- package/dist/components/game/organisms/utils/spriteAnimation.d.ts +29 -1
- package/dist/components/index.cjs +377 -141
- package/dist/components/index.js +378 -140
- package/dist/providers/index.cjs +289 -28
- package/dist/providers/index.js +290 -29
- package/dist/runtime/index.cjs +519 -215
- package/dist/runtime/index.js +521 -217
- package/package.json +1 -1
package/dist/providers/index.js
CHANGED
|
@@ -41,12 +41,12 @@ import { DndContext, pointerWithin, rectIntersection, closestCorners, useSensors
|
|
|
41
41
|
import { useSortable, arrayMove, sortableKeyboardCoordinates, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
|
42
42
|
import { CSS } from '@dnd-kit/utilities';
|
|
43
43
|
import { useNodeId, ReactFlowProvider, Handle, Position } from '@xyflow/react';
|
|
44
|
-
import * as
|
|
44
|
+
import * as THREE3 from 'three';
|
|
45
45
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
46
46
|
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
|
|
47
47
|
import { GLTFLoader as GLTFLoader$1 } from 'three/examples/jsm/loaders/GLTFLoader';
|
|
48
|
-
import { Canvas, useThree } from '@react-three/fiber';
|
|
49
|
-
import { Grid as Grid$1, OrbitControls } from '@react-three/drei';
|
|
48
|
+
import { Canvas, useLoader, useFrame, useThree } from '@react-three/fiber';
|
|
49
|
+
import { Billboard, Grid as Grid$1, OrbitControls } from '@react-three/drei';
|
|
50
50
|
import { getPatternDefinition, getComponentForPattern as getComponentForPattern$1 } from '@almadar/patterns';
|
|
51
51
|
|
|
52
52
|
var __defProp = Object.defineProperty;
|
|
@@ -7011,6 +7011,52 @@ var init_ControlButton = __esm({
|
|
|
7011
7011
|
ControlButton.displayName = "ControlButton";
|
|
7012
7012
|
}
|
|
7013
7013
|
});
|
|
7014
|
+
|
|
7015
|
+
// components/game/organisms/utils/spriteAnimation.ts
|
|
7016
|
+
function inferDirection(dx, dy) {
|
|
7017
|
+
if (dx === 0 && dy === 0) return "se";
|
|
7018
|
+
if (dx >= 0 && dy >= 0) return "se";
|
|
7019
|
+
if (dx <= 0 && dy >= 0) return "sw";
|
|
7020
|
+
if (dx >= 0 && dy <= 0) return "ne";
|
|
7021
|
+
return "nw";
|
|
7022
|
+
}
|
|
7023
|
+
function resolveSheetDirection(facing) {
|
|
7024
|
+
switch (facing) {
|
|
7025
|
+
case "se":
|
|
7026
|
+
return { sheetDir: "se", flipX: false };
|
|
7027
|
+
case "sw":
|
|
7028
|
+
return { sheetDir: "sw", flipX: false };
|
|
7029
|
+
case "ne":
|
|
7030
|
+
return { sheetDir: "sw", flipX: true };
|
|
7031
|
+
case "nw":
|
|
7032
|
+
return { sheetDir: "se", flipX: true };
|
|
7033
|
+
}
|
|
7034
|
+
}
|
|
7035
|
+
function frameRect(frame, row, columns, frameWidth, frameHeight) {
|
|
7036
|
+
return {
|
|
7037
|
+
sx: frame % columns * frameWidth,
|
|
7038
|
+
sy: row * frameHeight,
|
|
7039
|
+
sw: frameWidth,
|
|
7040
|
+
sh: frameHeight
|
|
7041
|
+
};
|
|
7042
|
+
}
|
|
7043
|
+
function getCurrentFrameFromDef(def, elapsed) {
|
|
7044
|
+
const frameDuration = 1e3 / def.frameRate;
|
|
7045
|
+
const totalDuration = def.frames * frameDuration;
|
|
7046
|
+
if (def.loop) {
|
|
7047
|
+
const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
|
|
7048
|
+
return { frame: frame2, finished: false };
|
|
7049
|
+
}
|
|
7050
|
+
if (elapsed >= totalDuration) {
|
|
7051
|
+
return { frame: def.frames - 1, finished: true };
|
|
7052
|
+
}
|
|
7053
|
+
const frame = Math.floor(elapsed / frameDuration);
|
|
7054
|
+
return { frame, finished: false };
|
|
7055
|
+
}
|
|
7056
|
+
var init_spriteAnimation = __esm({
|
|
7057
|
+
"components/game/organisms/utils/spriteAnimation.ts"() {
|
|
7058
|
+
}
|
|
7059
|
+
});
|
|
7014
7060
|
function Sprite({
|
|
7015
7061
|
spritesheet = "https://almadar-kflow-assets.web.app/shared/isometric-blocks/Spritesheet/allTiles_sheet.png",
|
|
7016
7062
|
frameWidth = 64,
|
|
@@ -7031,12 +7077,8 @@ function Sprite({
|
|
|
7031
7077
|
}) {
|
|
7032
7078
|
const eventBus = useEventBus();
|
|
7033
7079
|
const sourcePosition = useMemo(() => {
|
|
7034
|
-
const
|
|
7035
|
-
|
|
7036
|
-
return {
|
|
7037
|
-
x: frameX * frameWidth,
|
|
7038
|
-
y: frameY * frameHeight
|
|
7039
|
-
};
|
|
7080
|
+
const { sx, sy } = frameRect(frame, Math.floor(frame / columns), columns, frameWidth, frameHeight);
|
|
7081
|
+
return { x: sx, y: sy };
|
|
7040
7082
|
}, [frame, columns, frameWidth, frameHeight]);
|
|
7041
7083
|
const transform = useMemo(() => {
|
|
7042
7084
|
const transforms = [
|
|
@@ -7084,6 +7126,7 @@ var init_Sprite = __esm({
|
|
|
7084
7126
|
"components/game/atoms/Sprite.tsx"() {
|
|
7085
7127
|
"use client";
|
|
7086
7128
|
init_useEventBus();
|
|
7129
|
+
init_spriteAnimation();
|
|
7087
7130
|
}
|
|
7088
7131
|
});
|
|
7089
7132
|
function StateIndicator({
|
|
@@ -10819,6 +10862,163 @@ var init_useCamera = __esm({
|
|
|
10819
10862
|
"use client";
|
|
10820
10863
|
}
|
|
10821
10864
|
});
|
|
10865
|
+
function unitAtlasUrl(unit) {
|
|
10866
|
+
if (unit.spriteSheet) return unit.spriteSheet;
|
|
10867
|
+
const sprite = unit.sprite;
|
|
10868
|
+
if (!sprite) return null;
|
|
10869
|
+
const match = /^(.*-sprite-sheet)(?:-(?:se|sw))?(?:-v\d+)?\.png$/.exec(sprite);
|
|
10870
|
+
if (!match) return null;
|
|
10871
|
+
return `${match[1]}.json`;
|
|
10872
|
+
}
|
|
10873
|
+
function resolveSheetUrl(atlasUrl, relativeSheetPath) {
|
|
10874
|
+
try {
|
|
10875
|
+
return new URL(relativeSheetPath, atlasUrl).toString();
|
|
10876
|
+
} catch {
|
|
10877
|
+
const base = atlasUrl.slice(0, atlasUrl.lastIndexOf("/") + 1);
|
|
10878
|
+
return `${base}${relativeSheetPath.replace(/^\.\//, "")}`;
|
|
10879
|
+
}
|
|
10880
|
+
}
|
|
10881
|
+
function useUnitSpriteAtlas(units) {
|
|
10882
|
+
const atlasCacheRef = useRef(/* @__PURE__ */ new Map());
|
|
10883
|
+
const loadingRef = useRef(/* @__PURE__ */ new Set());
|
|
10884
|
+
const [pendingCount, setPendingCount] = useState(0);
|
|
10885
|
+
const [, forceTick] = useState(0);
|
|
10886
|
+
const animStatesRef = useRef(/* @__PURE__ */ new Map());
|
|
10887
|
+
const lastTickRef = useRef(0);
|
|
10888
|
+
const rafRef = useRef(0);
|
|
10889
|
+
const atlasUrls = useMemo(() => {
|
|
10890
|
+
const set = /* @__PURE__ */ new Set();
|
|
10891
|
+
for (const unit of units) {
|
|
10892
|
+
const url = unitAtlasUrl(unit);
|
|
10893
|
+
if (url) set.add(url);
|
|
10894
|
+
}
|
|
10895
|
+
return [...set];
|
|
10896
|
+
}, [units]);
|
|
10897
|
+
useEffect(() => {
|
|
10898
|
+
const cache = atlasCacheRef.current;
|
|
10899
|
+
const loading = loadingRef.current;
|
|
10900
|
+
const toLoad = atlasUrls.filter((url) => !cache.has(url) && !loading.has(url));
|
|
10901
|
+
if (toLoad.length === 0) return;
|
|
10902
|
+
let cancelled = false;
|
|
10903
|
+
setPendingCount((prev) => prev + toLoad.length);
|
|
10904
|
+
for (const url of toLoad) {
|
|
10905
|
+
loading.add(url);
|
|
10906
|
+
fetch(url).then((res) => res.ok ? res.json() : Promise.reject(new Error(String(res.status)))).then((atlas) => {
|
|
10907
|
+
if (cancelled) return;
|
|
10908
|
+
cache.set(url, atlas);
|
|
10909
|
+
}).catch(() => {
|
|
10910
|
+
}).finally(() => {
|
|
10911
|
+
if (cancelled) return;
|
|
10912
|
+
loading.delete(url);
|
|
10913
|
+
setPendingCount((prev) => Math.max(0, prev - 1));
|
|
10914
|
+
forceTick((n) => n + 1);
|
|
10915
|
+
});
|
|
10916
|
+
}
|
|
10917
|
+
return () => {
|
|
10918
|
+
cancelled = true;
|
|
10919
|
+
};
|
|
10920
|
+
}, [atlasUrls]);
|
|
10921
|
+
const sheetUrls = useMemo(() => {
|
|
10922
|
+
const urls = /* @__PURE__ */ new Set();
|
|
10923
|
+
for (const unit of units) {
|
|
10924
|
+
const atlasUrl = unitAtlasUrl(unit);
|
|
10925
|
+
if (!atlasUrl) continue;
|
|
10926
|
+
const atlas = atlasCacheRef.current.get(atlasUrl);
|
|
10927
|
+
if (!atlas) continue;
|
|
10928
|
+
for (const rel of Object.values(atlas.sheets)) {
|
|
10929
|
+
if (rel) urls.add(resolveSheetUrl(atlasUrl, rel));
|
|
10930
|
+
}
|
|
10931
|
+
}
|
|
10932
|
+
return [...urls];
|
|
10933
|
+
}, [units, pendingCount]);
|
|
10934
|
+
useEffect(() => {
|
|
10935
|
+
const hasAtlasUnits = units.some((u) => unitAtlasUrl(u) !== null);
|
|
10936
|
+
if (!hasAtlasUnits) return;
|
|
10937
|
+
let running = true;
|
|
10938
|
+
const tick = (ts) => {
|
|
10939
|
+
if (!running) return;
|
|
10940
|
+
const last = lastTickRef.current || ts;
|
|
10941
|
+
const delta = ts - last;
|
|
10942
|
+
lastTickRef.current = ts;
|
|
10943
|
+
const states = animStatesRef.current;
|
|
10944
|
+
const currentIds = /* @__PURE__ */ new Set();
|
|
10945
|
+
for (const unit of units) {
|
|
10946
|
+
if (unitAtlasUrl(unit) === null) continue;
|
|
10947
|
+
currentIds.add(unit.id);
|
|
10948
|
+
let state = states.get(unit.id);
|
|
10949
|
+
if (!state) {
|
|
10950
|
+
state = { animation: "idle", direction: "se", elapsed: 0, walkHold: 0, prev: null };
|
|
10951
|
+
states.set(unit.id, state);
|
|
10952
|
+
}
|
|
10953
|
+
const posX = unit.position?.x ?? unit.x ?? 0;
|
|
10954
|
+
const posY = unit.position?.y ?? unit.y ?? 0;
|
|
10955
|
+
if (state.prev) {
|
|
10956
|
+
const dx = posX - state.prev.x;
|
|
10957
|
+
const dy = posY - state.prev.y;
|
|
10958
|
+
if (dx !== 0 || dy !== 0) {
|
|
10959
|
+
state.animation = "walk";
|
|
10960
|
+
state.direction = inferDirection(dx, dy);
|
|
10961
|
+
state.walkHold = WALK_HOLD_MS;
|
|
10962
|
+
} else if (state.animation === "walk") {
|
|
10963
|
+
state.walkHold -= delta;
|
|
10964
|
+
if (state.walkHold <= 0) state.animation = "idle";
|
|
10965
|
+
}
|
|
10966
|
+
}
|
|
10967
|
+
state.prev = { x: posX, y: posY };
|
|
10968
|
+
state.elapsed += delta;
|
|
10969
|
+
}
|
|
10970
|
+
for (const id of states.keys()) {
|
|
10971
|
+
if (!currentIds.has(id)) states.delete(id);
|
|
10972
|
+
}
|
|
10973
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
10974
|
+
};
|
|
10975
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
10976
|
+
return () => {
|
|
10977
|
+
running = false;
|
|
10978
|
+
cancelAnimationFrame(rafRef.current);
|
|
10979
|
+
lastTickRef.current = 0;
|
|
10980
|
+
};
|
|
10981
|
+
}, [units]);
|
|
10982
|
+
const resolveUnitFrame = useCallback((unitId) => {
|
|
10983
|
+
const unit = units.find((u) => u.id === unitId);
|
|
10984
|
+
if (!unit) return null;
|
|
10985
|
+
const atlasUrl = unitAtlasUrl(unit);
|
|
10986
|
+
if (!atlasUrl) return null;
|
|
10987
|
+
const atlas = atlasCacheRef.current.get(atlasUrl);
|
|
10988
|
+
if (!atlas) return null;
|
|
10989
|
+
const state = animStatesRef.current.get(unitId);
|
|
10990
|
+
const animation = state?.animation ?? "idle";
|
|
10991
|
+
const direction = state?.direction ?? "se";
|
|
10992
|
+
const elapsed = state?.elapsed ?? 0;
|
|
10993
|
+
const def = atlas.animations[animation] ?? atlas.animations.idle;
|
|
10994
|
+
if (!def) return null;
|
|
10995
|
+
const { sheetDir, flipX } = resolveSheetDirection(direction);
|
|
10996
|
+
const rel = atlas.sheets[sheetDir] ?? atlas.sheets.se ?? atlas.sheets.sw;
|
|
10997
|
+
if (!rel) return null;
|
|
10998
|
+
const sheetUrl = resolveSheetUrl(atlasUrl, rel);
|
|
10999
|
+
const isIdle = animation === "idle";
|
|
11000
|
+
const frame = isIdle ? 0 : getCurrentFrameFromDef(def, elapsed).frame;
|
|
11001
|
+
const rect = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
|
|
11002
|
+
return {
|
|
11003
|
+
sheetUrl,
|
|
11004
|
+
sx: rect.sx,
|
|
11005
|
+
sy: rect.sy,
|
|
11006
|
+
sw: rect.sw,
|
|
11007
|
+
sh: rect.sh,
|
|
11008
|
+
flipX,
|
|
11009
|
+
applyBreathing: isIdle
|
|
11010
|
+
};
|
|
11011
|
+
}, [units]);
|
|
11012
|
+
return { sheetUrls, resolveUnitFrame, pendingCount };
|
|
11013
|
+
}
|
|
11014
|
+
var WALK_HOLD_MS;
|
|
11015
|
+
var init_useUnitSpriteAtlas = __esm({
|
|
11016
|
+
"components/game/molecules/useUnitSpriteAtlas.ts"() {
|
|
11017
|
+
"use client";
|
|
11018
|
+
init_spriteAnimation();
|
|
11019
|
+
WALK_HOLD_MS = 600;
|
|
11020
|
+
}
|
|
11021
|
+
});
|
|
10822
11022
|
|
|
10823
11023
|
// components/game/organisms/utils/isometric.ts
|
|
10824
11024
|
function isoToScreen(tileX, tileY, scale, baseOffsetX) {
|
|
@@ -10933,6 +11133,10 @@ function IsometricCanvas({
|
|
|
10933
11133
|
() => unitsProp.map((u) => u.position ? u : { ...u, position: { x: u.x ?? 0, y: u.y ?? 0 } }),
|
|
10934
11134
|
[unitsProp]
|
|
10935
11135
|
);
|
|
11136
|
+
const { sheetUrls: atlasSheetUrls, resolveUnitFrame: resolveUnitFrameInternal, pendingCount: atlasPending } = useUnitSpriteAtlas(units);
|
|
11137
|
+
const resolveFrameForUnit = useCallback((unitId) => {
|
|
11138
|
+
return resolveUnitFrame?.(unitId) ?? resolveUnitFrameInternal(unitId);
|
|
11139
|
+
}, [resolveUnitFrame, resolveUnitFrameInternal]);
|
|
10936
11140
|
const features = useMemo(
|
|
10937
11141
|
() => featuresProp.map((f3) => {
|
|
10938
11142
|
if (f3.type) return f3;
|
|
@@ -11016,9 +11220,10 @@ function IsometricCanvas({
|
|
|
11016
11220
|
}
|
|
11017
11221
|
}
|
|
11018
11222
|
if (effectSpriteUrls) urls.push(...effectSpriteUrls);
|
|
11223
|
+
if (atlasSheetUrls.length) urls.push(...atlasSheetUrls);
|
|
11019
11224
|
if (backgroundImage) urls.push(backgroundImage);
|
|
11020
11225
|
return [...new Set(urls.filter(Boolean))];
|
|
11021
|
-
}, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, backgroundImage, assetManifest, resolveManifestUrl]);
|
|
11226
|
+
}, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, atlasSheetUrls, backgroundImage, assetManifest, resolveManifestUrl]);
|
|
11022
11227
|
const { getImage, pendingCount } = useImageCache(spriteUrls);
|
|
11023
11228
|
useEffect(() => {
|
|
11024
11229
|
if (typeof window === "undefined") return;
|
|
@@ -11305,7 +11510,7 @@ function IsometricCanvas({
|
|
|
11305
11510
|
ctx.lineWidth = 3;
|
|
11306
11511
|
ctx.stroke();
|
|
11307
11512
|
}
|
|
11308
|
-
const frame =
|
|
11513
|
+
const frame = resolveFrameForUnit(unit.id);
|
|
11309
11514
|
const frameImg = frame ? getImage(frame.sheetUrl) : null;
|
|
11310
11515
|
if (frame && frameImg) {
|
|
11311
11516
|
const frameAr = frame.sw / frame.sh;
|
|
@@ -11415,7 +11620,7 @@ function IsometricCanvas({
|
|
|
11415
11620
|
resolveTerrainSpriteUrl,
|
|
11416
11621
|
resolveFeatureSpriteUrl,
|
|
11417
11622
|
resolveUnitSpriteUrl,
|
|
11418
|
-
|
|
11623
|
+
resolveFrameForUnit,
|
|
11419
11624
|
getImage,
|
|
11420
11625
|
gridWidth,
|
|
11421
11626
|
gridHeight,
|
|
@@ -11447,7 +11652,7 @@ function IsometricCanvas({
|
|
|
11447
11652
|
};
|
|
11448
11653
|
}, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
|
|
11449
11654
|
useEffect(() => {
|
|
11450
|
-
const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0;
|
|
11655
|
+
const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0 || atlasPending > 0;
|
|
11451
11656
|
draw(animTimeRef.current);
|
|
11452
11657
|
if (!hasAnimations) return;
|
|
11453
11658
|
let running = true;
|
|
@@ -11463,7 +11668,7 @@ function IsometricCanvas({
|
|
|
11463
11668
|
running = false;
|
|
11464
11669
|
cancelAnimationFrame(rafIdRef.current);
|
|
11465
11670
|
};
|
|
11466
|
-
}, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId, hasActiveEffects2, pendingCount, lerpToTarget, targetCameraRef]);
|
|
11671
|
+
}, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId, hasActiveEffects2, pendingCount, atlasPending, lerpToTarget, targetCameraRef]);
|
|
11467
11672
|
const handleMouseMoveWithCamera = useCallback((e) => {
|
|
11468
11673
|
if (enableCamera) {
|
|
11469
11674
|
const wasPanning = handleMouseMove(e, () => draw(animTimeRef.current));
|
|
@@ -11608,6 +11813,7 @@ var init_IsometricCanvas = __esm({
|
|
|
11608
11813
|
init_ErrorState();
|
|
11609
11814
|
init_useImageCache();
|
|
11610
11815
|
init_useCamera();
|
|
11816
|
+
init_useUnitSpriteAtlas();
|
|
11611
11817
|
init_verificationRegistry();
|
|
11612
11818
|
init_isometric();
|
|
11613
11819
|
IsometricCanvas.displayName = "IsometricCanvas";
|
|
@@ -29190,13 +29396,13 @@ var init_MapView = __esm({
|
|
|
29190
29396
|
shadowSize: [41, 41]
|
|
29191
29397
|
});
|
|
29192
29398
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
29193
|
-
const { useEffect:
|
|
29399
|
+
const { useEffect: useEffect79, useRef: useRef75, useCallback: useCallback118, useState: useState109 } = React82__default;
|
|
29194
29400
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
29195
29401
|
const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
29196
29402
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
29197
29403
|
const map = useMap();
|
|
29198
|
-
const prevRef =
|
|
29199
|
-
|
|
29404
|
+
const prevRef = useRef75({ centerLat, centerLng, zoom });
|
|
29405
|
+
useEffect79(() => {
|
|
29200
29406
|
const prev = prevRef.current;
|
|
29201
29407
|
if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
|
|
29202
29408
|
map.setView([centerLat, centerLng], zoom);
|
|
@@ -29207,7 +29413,7 @@ var init_MapView = __esm({
|
|
|
29207
29413
|
}
|
|
29208
29414
|
function MapClickHandler({ onMapClick }) {
|
|
29209
29415
|
const map = useMap();
|
|
29210
|
-
|
|
29416
|
+
useEffect79(() => {
|
|
29211
29417
|
if (!onMapClick) return;
|
|
29212
29418
|
const handler = (e) => {
|
|
29213
29419
|
onMapClick(e.latlng.lat, e.latlng.lng);
|
|
@@ -29235,8 +29441,8 @@ var init_MapView = __esm({
|
|
|
29235
29441
|
showAttribution = true
|
|
29236
29442
|
}) {
|
|
29237
29443
|
const eventBus = useEventBus3();
|
|
29238
|
-
const [clickedPosition, setClickedPosition] =
|
|
29239
|
-
const handleMapClick =
|
|
29444
|
+
const [clickedPosition, setClickedPosition] = useState109(null);
|
|
29445
|
+
const handleMapClick = useCallback118((lat, lng) => {
|
|
29240
29446
|
if (showClickedPin) {
|
|
29241
29447
|
setClickedPosition({ lat, lng });
|
|
29242
29448
|
}
|
|
@@ -29245,7 +29451,7 @@ var init_MapView = __esm({
|
|
|
29245
29451
|
eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
|
|
29246
29452
|
}
|
|
29247
29453
|
}, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
|
|
29248
|
-
const handleMarkerClick =
|
|
29454
|
+
const handleMarkerClick = useCallback118((marker) => {
|
|
29249
29455
|
onMarkerClick?.(marker);
|
|
29250
29456
|
if (markerClickEvent) {
|
|
29251
29457
|
eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
|
|
@@ -40963,7 +41169,7 @@ var init_AssetLoader = __esm({
|
|
|
40963
41169
|
__publicField(this, "textureCache");
|
|
40964
41170
|
__publicField(this, "loadingPromises");
|
|
40965
41171
|
this.objLoader = new OBJLoader();
|
|
40966
|
-
this.textureLoader = new
|
|
41172
|
+
this.textureLoader = new THREE3.TextureLoader();
|
|
40967
41173
|
this.modelCache = /* @__PURE__ */ new Map();
|
|
40968
41174
|
this.textureCache = /* @__PURE__ */ new Map();
|
|
40969
41175
|
this.loadingPromises = /* @__PURE__ */ new Map();
|
|
@@ -41037,7 +41243,7 @@ var init_AssetLoader = __esm({
|
|
|
41037
41243
|
return this.loadingPromises.get(`texture:${url}`);
|
|
41038
41244
|
}
|
|
41039
41245
|
const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
|
|
41040
|
-
texture.colorSpace =
|
|
41246
|
+
texture.colorSpace = THREE3.SRGBColorSpace;
|
|
41041
41247
|
this.textureCache.set(url, texture);
|
|
41042
41248
|
this.loadingPromises.delete(`texture:${url}`);
|
|
41043
41249
|
return texture;
|
|
@@ -41111,7 +41317,7 @@ var init_AssetLoader = __esm({
|
|
|
41111
41317
|
});
|
|
41112
41318
|
this.modelCache.forEach((model) => {
|
|
41113
41319
|
model.scene.traverse((child) => {
|
|
41114
|
-
if (child instanceof
|
|
41320
|
+
if (child instanceof THREE3.Mesh) {
|
|
41115
41321
|
child.geometry.dispose();
|
|
41116
41322
|
if (Array.isArray(child.material)) {
|
|
41117
41323
|
child.material.forEach((m) => m.dispose());
|
|
@@ -41667,7 +41873,7 @@ function ModelLoader({
|
|
|
41667
41873
|
if (!loadedModel) return null;
|
|
41668
41874
|
const cloned = loadedModel.clone();
|
|
41669
41875
|
cloned.traverse((child) => {
|
|
41670
|
-
if (child instanceof
|
|
41876
|
+
if (child instanceof THREE3.Mesh) {
|
|
41671
41877
|
child.castShadow = castShadow;
|
|
41672
41878
|
child.receiveShadow = receiveShadow;
|
|
41673
41879
|
}
|
|
@@ -41781,6 +41987,47 @@ function CameraController({
|
|
|
41781
41987
|
}, [camera.position, onCameraChange]);
|
|
41782
41988
|
return null;
|
|
41783
41989
|
}
|
|
41990
|
+
function UnitSpriteBillboard({
|
|
41991
|
+
sheetUrl,
|
|
41992
|
+
resolveFrame,
|
|
41993
|
+
height = 1.2
|
|
41994
|
+
}) {
|
|
41995
|
+
const texture = useLoader(THREE3.TextureLoader, sheetUrl);
|
|
41996
|
+
const meshRef = useRef(null);
|
|
41997
|
+
const matRef = useRef(null);
|
|
41998
|
+
const [aspect, setAspect] = useState(1);
|
|
41999
|
+
useFrame(() => {
|
|
42000
|
+
const frame = resolveFrame();
|
|
42001
|
+
if (!frame || !texture.image) return;
|
|
42002
|
+
const imgW = texture.image.width;
|
|
42003
|
+
const imgH = texture.image.height;
|
|
42004
|
+
if (!imgW || !imgH) return;
|
|
42005
|
+
texture.repeat.set((frame.flipX ? -1 : 1) * (frame.sw / imgW), frame.sh / imgH);
|
|
42006
|
+
texture.offset.set(
|
|
42007
|
+
frame.flipX ? (frame.sx + frame.sw) / imgW : frame.sx / imgW,
|
|
42008
|
+
1 - (frame.sy + frame.sh) / imgH
|
|
42009
|
+
);
|
|
42010
|
+
texture.magFilter = THREE3.NearestFilter;
|
|
42011
|
+
texture.minFilter = THREE3.NearestFilter;
|
|
42012
|
+
texture.needsUpdate = true;
|
|
42013
|
+
const nextAspect = frame.sw / frame.sh;
|
|
42014
|
+
if (Math.abs(nextAspect - aspect) > 1e-3) setAspect(nextAspect);
|
|
42015
|
+
if (matRef.current) matRef.current.needsUpdate = true;
|
|
42016
|
+
});
|
|
42017
|
+
return /* @__PURE__ */ jsxs("mesh", { ref: meshRef, position: [0, height / 2, 0], children: [
|
|
42018
|
+
/* @__PURE__ */ jsx("planeGeometry", { args: [height * aspect, height] }),
|
|
42019
|
+
/* @__PURE__ */ jsx(
|
|
42020
|
+
"meshBasicMaterial",
|
|
42021
|
+
{
|
|
42022
|
+
ref: matRef,
|
|
42023
|
+
map: texture,
|
|
42024
|
+
transparent: true,
|
|
42025
|
+
alphaTest: 0.1,
|
|
42026
|
+
side: THREE3.DoubleSide
|
|
42027
|
+
}
|
|
42028
|
+
)
|
|
42029
|
+
] });
|
|
42030
|
+
}
|
|
41784
42031
|
var DEFAULT_GRID_CONFIG, GameCanvas3D;
|
|
41785
42032
|
var init_GameCanvas3D2 = __esm({
|
|
41786
42033
|
"components/game/molecules/GameCanvas3D.tsx"() {
|
|
@@ -41791,6 +42038,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41791
42038
|
init_Canvas3DLoadingState2();
|
|
41792
42039
|
init_Canvas3DErrorBoundary2();
|
|
41793
42040
|
init_ModelLoader();
|
|
42041
|
+
init_useUnitSpriteAtlas();
|
|
41794
42042
|
init_cn();
|
|
41795
42043
|
init_GameCanvas3D();
|
|
41796
42044
|
DEFAULT_GRID_CONFIG = {
|
|
@@ -41847,8 +42095,10 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41847
42095
|
const controlsRef = useRef(null);
|
|
41848
42096
|
const [hoveredTile, setHoveredTile] = useState(null);
|
|
41849
42097
|
const [internalError, setInternalError] = useState(null);
|
|
42098
|
+
const { sheetUrls: atlasSheetUrls, resolveUnitFrame } = useUnitSpriteAtlas(units);
|
|
42099
|
+
const preloadUrls = useMemo(() => [...preloadAssets, ...atlasSheetUrls], [preloadAssets, atlasSheetUrls]);
|
|
41850
42100
|
const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
|
|
41851
|
-
preloadUrls
|
|
42101
|
+
preloadUrls,
|
|
41852
42102
|
loader: customAssetLoader
|
|
41853
42103
|
});
|
|
41854
42104
|
const eventHandlers = useGameCanvas3DEvents({
|
|
@@ -41907,7 +42157,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41907
42157
|
getCameraPosition: () => {
|
|
41908
42158
|
if (controlsRef.current) {
|
|
41909
42159
|
const pos = controlsRef.current.object.position;
|
|
41910
|
-
return new
|
|
42160
|
+
return new THREE3.Vector3(pos.x, pos.y, pos.z);
|
|
41911
42161
|
}
|
|
41912
42162
|
return null;
|
|
41913
42163
|
},
|
|
@@ -42059,6 +42309,8 @@ var init_GameCanvas3D2 = __esm({
|
|
|
42059
42309
|
({ unit, position }) => {
|
|
42060
42310
|
const isSelected = selectedUnitId === unit.id;
|
|
42061
42311
|
const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
|
|
42312
|
+
const hasAtlas = unitAtlasUrl(unit) !== null;
|
|
42313
|
+
const initialFrame = hasAtlas ? resolveUnitFrame(unit.id) : null;
|
|
42062
42314
|
return /* @__PURE__ */ jsxs(
|
|
42063
42315
|
"group",
|
|
42064
42316
|
{
|
|
@@ -42070,7 +42322,16 @@ var init_GameCanvas3D2 = __esm({
|
|
|
42070
42322
|
/* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
|
|
42071
42323
|
/* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
|
|
42072
42324
|
] }),
|
|
42073
|
-
|
|
42325
|
+
hasAtlas && initialFrame ? (
|
|
42326
|
+
/* Animated sprite-sheet billboard — single cropped frame, by state */
|
|
42327
|
+
/* @__PURE__ */ jsx(Billboard, { children: /* @__PURE__ */ jsx(
|
|
42328
|
+
UnitSpriteBillboard,
|
|
42329
|
+
{
|
|
42330
|
+
sheetUrl: initialFrame.sheetUrl,
|
|
42331
|
+
resolveFrame: () => resolveUnitFrame(unit.id)
|
|
42332
|
+
}
|
|
42333
|
+
) })
|
|
42334
|
+
) : unit.modelUrl ? (
|
|
42074
42335
|
/* GLB unit model (box fallback while loading / on error) */
|
|
42075
42336
|
/* @__PURE__ */ jsx(
|
|
42076
42337
|
ModelLoader,
|
|
@@ -42124,7 +42385,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
42124
42385
|
}
|
|
42125
42386
|
);
|
|
42126
42387
|
},
|
|
42127
|
-
[selectedUnitId, handleUnitClick]
|
|
42388
|
+
[selectedUnitId, handleUnitClick, resolveUnitFrame]
|
|
42128
42389
|
);
|
|
42129
42390
|
const DefaultFeatureRenderer = useCallback(
|
|
42130
42391
|
({
|