@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.cjs
CHANGED
|
@@ -41,7 +41,7 @@ var core$1 = require('@dnd-kit/core');
|
|
|
41
41
|
var sortable = require('@dnd-kit/sortable');
|
|
42
42
|
var utilities = require('@dnd-kit/utilities');
|
|
43
43
|
var react = require('@xyflow/react');
|
|
44
|
-
var
|
|
44
|
+
var THREE3 = require('three');
|
|
45
45
|
var GLTFLoader_js = require('three/examples/jsm/loaders/GLTFLoader.js');
|
|
46
46
|
var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
|
|
47
47
|
var GLTFLoader = require('three/examples/jsm/loaders/GLTFLoader');
|
|
@@ -94,7 +94,7 @@ var ReactMarkdown__default = /*#__PURE__*/_interopDefault(ReactMarkdown);
|
|
|
94
94
|
var remarkGfm__default = /*#__PURE__*/_interopDefault(remarkGfm);
|
|
95
95
|
var remarkMath__default = /*#__PURE__*/_interopDefault(remarkMath);
|
|
96
96
|
var rehypeKatex__default = /*#__PURE__*/_interopDefault(rehypeKatex);
|
|
97
|
-
var
|
|
97
|
+
var THREE3__namespace = /*#__PURE__*/_interopNamespace(THREE3);
|
|
98
98
|
|
|
99
99
|
var __defProp = Object.defineProperty;
|
|
100
100
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -7058,6 +7058,52 @@ var init_ControlButton = __esm({
|
|
|
7058
7058
|
ControlButton.displayName = "ControlButton";
|
|
7059
7059
|
}
|
|
7060
7060
|
});
|
|
7061
|
+
|
|
7062
|
+
// components/game/organisms/utils/spriteAnimation.ts
|
|
7063
|
+
function inferDirection(dx, dy) {
|
|
7064
|
+
if (dx === 0 && dy === 0) return "se";
|
|
7065
|
+
if (dx >= 0 && dy >= 0) return "se";
|
|
7066
|
+
if (dx <= 0 && dy >= 0) return "sw";
|
|
7067
|
+
if (dx >= 0 && dy <= 0) return "ne";
|
|
7068
|
+
return "nw";
|
|
7069
|
+
}
|
|
7070
|
+
function resolveSheetDirection(facing) {
|
|
7071
|
+
switch (facing) {
|
|
7072
|
+
case "se":
|
|
7073
|
+
return { sheetDir: "se", flipX: false };
|
|
7074
|
+
case "sw":
|
|
7075
|
+
return { sheetDir: "sw", flipX: false };
|
|
7076
|
+
case "ne":
|
|
7077
|
+
return { sheetDir: "sw", flipX: true };
|
|
7078
|
+
case "nw":
|
|
7079
|
+
return { sheetDir: "se", flipX: true };
|
|
7080
|
+
}
|
|
7081
|
+
}
|
|
7082
|
+
function frameRect(frame, row, columns, frameWidth, frameHeight) {
|
|
7083
|
+
return {
|
|
7084
|
+
sx: frame % columns * frameWidth,
|
|
7085
|
+
sy: row * frameHeight,
|
|
7086
|
+
sw: frameWidth,
|
|
7087
|
+
sh: frameHeight
|
|
7088
|
+
};
|
|
7089
|
+
}
|
|
7090
|
+
function getCurrentFrameFromDef(def, elapsed) {
|
|
7091
|
+
const frameDuration = 1e3 / def.frameRate;
|
|
7092
|
+
const totalDuration = def.frames * frameDuration;
|
|
7093
|
+
if (def.loop) {
|
|
7094
|
+
const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
|
|
7095
|
+
return { frame: frame2, finished: false };
|
|
7096
|
+
}
|
|
7097
|
+
if (elapsed >= totalDuration) {
|
|
7098
|
+
return { frame: def.frames - 1, finished: true };
|
|
7099
|
+
}
|
|
7100
|
+
const frame = Math.floor(elapsed / frameDuration);
|
|
7101
|
+
return { frame, finished: false };
|
|
7102
|
+
}
|
|
7103
|
+
var init_spriteAnimation = __esm({
|
|
7104
|
+
"components/game/organisms/utils/spriteAnimation.ts"() {
|
|
7105
|
+
}
|
|
7106
|
+
});
|
|
7061
7107
|
function Sprite({
|
|
7062
7108
|
spritesheet = "https://almadar-kflow-assets.web.app/shared/isometric-blocks/Spritesheet/allTiles_sheet.png",
|
|
7063
7109
|
frameWidth = 64,
|
|
@@ -7078,12 +7124,8 @@ function Sprite({
|
|
|
7078
7124
|
}) {
|
|
7079
7125
|
const eventBus = useEventBus();
|
|
7080
7126
|
const sourcePosition = React82.useMemo(() => {
|
|
7081
|
-
const
|
|
7082
|
-
|
|
7083
|
-
return {
|
|
7084
|
-
x: frameX * frameWidth,
|
|
7085
|
-
y: frameY * frameHeight
|
|
7086
|
-
};
|
|
7127
|
+
const { sx, sy } = frameRect(frame, Math.floor(frame / columns), columns, frameWidth, frameHeight);
|
|
7128
|
+
return { x: sx, y: sy };
|
|
7087
7129
|
}, [frame, columns, frameWidth, frameHeight]);
|
|
7088
7130
|
const transform = React82.useMemo(() => {
|
|
7089
7131
|
const transforms = [
|
|
@@ -7131,6 +7173,7 @@ var init_Sprite = __esm({
|
|
|
7131
7173
|
"components/game/atoms/Sprite.tsx"() {
|
|
7132
7174
|
"use client";
|
|
7133
7175
|
init_useEventBus();
|
|
7176
|
+
init_spriteAnimation();
|
|
7134
7177
|
}
|
|
7135
7178
|
});
|
|
7136
7179
|
function StateIndicator({
|
|
@@ -10866,6 +10909,163 @@ var init_useCamera = __esm({
|
|
|
10866
10909
|
"use client";
|
|
10867
10910
|
}
|
|
10868
10911
|
});
|
|
10912
|
+
function unitAtlasUrl(unit) {
|
|
10913
|
+
if (unit.spriteSheet) return unit.spriteSheet;
|
|
10914
|
+
const sprite = unit.sprite;
|
|
10915
|
+
if (!sprite) return null;
|
|
10916
|
+
const match = /^(.*-sprite-sheet)(?:-(?:se|sw))?(?:-v\d+)?\.png$/.exec(sprite);
|
|
10917
|
+
if (!match) return null;
|
|
10918
|
+
return `${match[1]}.json`;
|
|
10919
|
+
}
|
|
10920
|
+
function resolveSheetUrl(atlasUrl, relativeSheetPath) {
|
|
10921
|
+
try {
|
|
10922
|
+
return new URL(relativeSheetPath, atlasUrl).toString();
|
|
10923
|
+
} catch {
|
|
10924
|
+
const base = atlasUrl.slice(0, atlasUrl.lastIndexOf("/") + 1);
|
|
10925
|
+
return `${base}${relativeSheetPath.replace(/^\.\//, "")}`;
|
|
10926
|
+
}
|
|
10927
|
+
}
|
|
10928
|
+
function useUnitSpriteAtlas(units) {
|
|
10929
|
+
const atlasCacheRef = React82.useRef(/* @__PURE__ */ new Map());
|
|
10930
|
+
const loadingRef = React82.useRef(/* @__PURE__ */ new Set());
|
|
10931
|
+
const [pendingCount, setPendingCount] = React82.useState(0);
|
|
10932
|
+
const [, forceTick] = React82.useState(0);
|
|
10933
|
+
const animStatesRef = React82.useRef(/* @__PURE__ */ new Map());
|
|
10934
|
+
const lastTickRef = React82.useRef(0);
|
|
10935
|
+
const rafRef = React82.useRef(0);
|
|
10936
|
+
const atlasUrls = React82.useMemo(() => {
|
|
10937
|
+
const set = /* @__PURE__ */ new Set();
|
|
10938
|
+
for (const unit of units) {
|
|
10939
|
+
const url = unitAtlasUrl(unit);
|
|
10940
|
+
if (url) set.add(url);
|
|
10941
|
+
}
|
|
10942
|
+
return [...set];
|
|
10943
|
+
}, [units]);
|
|
10944
|
+
React82.useEffect(() => {
|
|
10945
|
+
const cache = atlasCacheRef.current;
|
|
10946
|
+
const loading = loadingRef.current;
|
|
10947
|
+
const toLoad = atlasUrls.filter((url) => !cache.has(url) && !loading.has(url));
|
|
10948
|
+
if (toLoad.length === 0) return;
|
|
10949
|
+
let cancelled = false;
|
|
10950
|
+
setPendingCount((prev) => prev + toLoad.length);
|
|
10951
|
+
for (const url of toLoad) {
|
|
10952
|
+
loading.add(url);
|
|
10953
|
+
fetch(url).then((res) => res.ok ? res.json() : Promise.reject(new Error(String(res.status)))).then((atlas) => {
|
|
10954
|
+
if (cancelled) return;
|
|
10955
|
+
cache.set(url, atlas);
|
|
10956
|
+
}).catch(() => {
|
|
10957
|
+
}).finally(() => {
|
|
10958
|
+
if (cancelled) return;
|
|
10959
|
+
loading.delete(url);
|
|
10960
|
+
setPendingCount((prev) => Math.max(0, prev - 1));
|
|
10961
|
+
forceTick((n) => n + 1);
|
|
10962
|
+
});
|
|
10963
|
+
}
|
|
10964
|
+
return () => {
|
|
10965
|
+
cancelled = true;
|
|
10966
|
+
};
|
|
10967
|
+
}, [atlasUrls]);
|
|
10968
|
+
const sheetUrls = React82.useMemo(() => {
|
|
10969
|
+
const urls = /* @__PURE__ */ new Set();
|
|
10970
|
+
for (const unit of units) {
|
|
10971
|
+
const atlasUrl = unitAtlasUrl(unit);
|
|
10972
|
+
if (!atlasUrl) continue;
|
|
10973
|
+
const atlas = atlasCacheRef.current.get(atlasUrl);
|
|
10974
|
+
if (!atlas) continue;
|
|
10975
|
+
for (const rel of Object.values(atlas.sheets)) {
|
|
10976
|
+
if (rel) urls.add(resolveSheetUrl(atlasUrl, rel));
|
|
10977
|
+
}
|
|
10978
|
+
}
|
|
10979
|
+
return [...urls];
|
|
10980
|
+
}, [units, pendingCount]);
|
|
10981
|
+
React82.useEffect(() => {
|
|
10982
|
+
const hasAtlasUnits = units.some((u) => unitAtlasUrl(u) !== null);
|
|
10983
|
+
if (!hasAtlasUnits) return;
|
|
10984
|
+
let running = true;
|
|
10985
|
+
const tick = (ts) => {
|
|
10986
|
+
if (!running) return;
|
|
10987
|
+
const last = lastTickRef.current || ts;
|
|
10988
|
+
const delta = ts - last;
|
|
10989
|
+
lastTickRef.current = ts;
|
|
10990
|
+
const states = animStatesRef.current;
|
|
10991
|
+
const currentIds = /* @__PURE__ */ new Set();
|
|
10992
|
+
for (const unit of units) {
|
|
10993
|
+
if (unitAtlasUrl(unit) === null) continue;
|
|
10994
|
+
currentIds.add(unit.id);
|
|
10995
|
+
let state = states.get(unit.id);
|
|
10996
|
+
if (!state) {
|
|
10997
|
+
state = { animation: "idle", direction: "se", elapsed: 0, walkHold: 0, prev: null };
|
|
10998
|
+
states.set(unit.id, state);
|
|
10999
|
+
}
|
|
11000
|
+
const posX = unit.position?.x ?? unit.x ?? 0;
|
|
11001
|
+
const posY = unit.position?.y ?? unit.y ?? 0;
|
|
11002
|
+
if (state.prev) {
|
|
11003
|
+
const dx = posX - state.prev.x;
|
|
11004
|
+
const dy = posY - state.prev.y;
|
|
11005
|
+
if (dx !== 0 || dy !== 0) {
|
|
11006
|
+
state.animation = "walk";
|
|
11007
|
+
state.direction = inferDirection(dx, dy);
|
|
11008
|
+
state.walkHold = WALK_HOLD_MS;
|
|
11009
|
+
} else if (state.animation === "walk") {
|
|
11010
|
+
state.walkHold -= delta;
|
|
11011
|
+
if (state.walkHold <= 0) state.animation = "idle";
|
|
11012
|
+
}
|
|
11013
|
+
}
|
|
11014
|
+
state.prev = { x: posX, y: posY };
|
|
11015
|
+
state.elapsed += delta;
|
|
11016
|
+
}
|
|
11017
|
+
for (const id of states.keys()) {
|
|
11018
|
+
if (!currentIds.has(id)) states.delete(id);
|
|
11019
|
+
}
|
|
11020
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
11021
|
+
};
|
|
11022
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
11023
|
+
return () => {
|
|
11024
|
+
running = false;
|
|
11025
|
+
cancelAnimationFrame(rafRef.current);
|
|
11026
|
+
lastTickRef.current = 0;
|
|
11027
|
+
};
|
|
11028
|
+
}, [units]);
|
|
11029
|
+
const resolveUnitFrame = React82.useCallback((unitId) => {
|
|
11030
|
+
const unit = units.find((u) => u.id === unitId);
|
|
11031
|
+
if (!unit) return null;
|
|
11032
|
+
const atlasUrl = unitAtlasUrl(unit);
|
|
11033
|
+
if (!atlasUrl) return null;
|
|
11034
|
+
const atlas = atlasCacheRef.current.get(atlasUrl);
|
|
11035
|
+
if (!atlas) return null;
|
|
11036
|
+
const state = animStatesRef.current.get(unitId);
|
|
11037
|
+
const animation = state?.animation ?? "idle";
|
|
11038
|
+
const direction = state?.direction ?? "se";
|
|
11039
|
+
const elapsed = state?.elapsed ?? 0;
|
|
11040
|
+
const def = atlas.animations[animation] ?? atlas.animations.idle;
|
|
11041
|
+
if (!def) return null;
|
|
11042
|
+
const { sheetDir, flipX } = resolveSheetDirection(direction);
|
|
11043
|
+
const rel = atlas.sheets[sheetDir] ?? atlas.sheets.se ?? atlas.sheets.sw;
|
|
11044
|
+
if (!rel) return null;
|
|
11045
|
+
const sheetUrl = resolveSheetUrl(atlasUrl, rel);
|
|
11046
|
+
const isIdle = animation === "idle";
|
|
11047
|
+
const frame = isIdle ? 0 : getCurrentFrameFromDef(def, elapsed).frame;
|
|
11048
|
+
const rect = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
|
|
11049
|
+
return {
|
|
11050
|
+
sheetUrl,
|
|
11051
|
+
sx: rect.sx,
|
|
11052
|
+
sy: rect.sy,
|
|
11053
|
+
sw: rect.sw,
|
|
11054
|
+
sh: rect.sh,
|
|
11055
|
+
flipX,
|
|
11056
|
+
applyBreathing: isIdle
|
|
11057
|
+
};
|
|
11058
|
+
}, [units]);
|
|
11059
|
+
return { sheetUrls, resolveUnitFrame, pendingCount };
|
|
11060
|
+
}
|
|
11061
|
+
var WALK_HOLD_MS;
|
|
11062
|
+
var init_useUnitSpriteAtlas = __esm({
|
|
11063
|
+
"components/game/molecules/useUnitSpriteAtlas.ts"() {
|
|
11064
|
+
"use client";
|
|
11065
|
+
init_spriteAnimation();
|
|
11066
|
+
WALK_HOLD_MS = 600;
|
|
11067
|
+
}
|
|
11068
|
+
});
|
|
10869
11069
|
|
|
10870
11070
|
// components/game/organisms/utils/isometric.ts
|
|
10871
11071
|
function isoToScreen(tileX, tileY, scale, baseOffsetX) {
|
|
@@ -10980,6 +11180,10 @@ function IsometricCanvas({
|
|
|
10980
11180
|
() => unitsProp.map((u) => u.position ? u : { ...u, position: { x: u.x ?? 0, y: u.y ?? 0 } }),
|
|
10981
11181
|
[unitsProp]
|
|
10982
11182
|
);
|
|
11183
|
+
const { sheetUrls: atlasSheetUrls, resolveUnitFrame: resolveUnitFrameInternal, pendingCount: atlasPending } = useUnitSpriteAtlas(units);
|
|
11184
|
+
const resolveFrameForUnit = React82.useCallback((unitId) => {
|
|
11185
|
+
return resolveUnitFrame?.(unitId) ?? resolveUnitFrameInternal(unitId);
|
|
11186
|
+
}, [resolveUnitFrame, resolveUnitFrameInternal]);
|
|
10983
11187
|
const features = React82.useMemo(
|
|
10984
11188
|
() => featuresProp.map((f3) => {
|
|
10985
11189
|
if (f3.type) return f3;
|
|
@@ -11063,9 +11267,10 @@ function IsometricCanvas({
|
|
|
11063
11267
|
}
|
|
11064
11268
|
}
|
|
11065
11269
|
if (effectSpriteUrls) urls.push(...effectSpriteUrls);
|
|
11270
|
+
if (atlasSheetUrls.length) urls.push(...atlasSheetUrls);
|
|
11066
11271
|
if (backgroundImage) urls.push(backgroundImage);
|
|
11067
11272
|
return [...new Set(urls.filter(Boolean))];
|
|
11068
|
-
}, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, backgroundImage, assetManifest, resolveManifestUrl]);
|
|
11273
|
+
}, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, atlasSheetUrls, backgroundImage, assetManifest, resolveManifestUrl]);
|
|
11069
11274
|
const { getImage, pendingCount } = useImageCache(spriteUrls);
|
|
11070
11275
|
React82.useEffect(() => {
|
|
11071
11276
|
if (typeof window === "undefined") return;
|
|
@@ -11352,7 +11557,7 @@ function IsometricCanvas({
|
|
|
11352
11557
|
ctx.lineWidth = 3;
|
|
11353
11558
|
ctx.stroke();
|
|
11354
11559
|
}
|
|
11355
|
-
const frame =
|
|
11560
|
+
const frame = resolveFrameForUnit(unit.id);
|
|
11356
11561
|
const frameImg = frame ? getImage(frame.sheetUrl) : null;
|
|
11357
11562
|
if (frame && frameImg) {
|
|
11358
11563
|
const frameAr = frame.sw / frame.sh;
|
|
@@ -11462,7 +11667,7 @@ function IsometricCanvas({
|
|
|
11462
11667
|
resolveTerrainSpriteUrl,
|
|
11463
11668
|
resolveFeatureSpriteUrl,
|
|
11464
11669
|
resolveUnitSpriteUrl,
|
|
11465
|
-
|
|
11670
|
+
resolveFrameForUnit,
|
|
11466
11671
|
getImage,
|
|
11467
11672
|
gridWidth,
|
|
11468
11673
|
gridHeight,
|
|
@@ -11494,7 +11699,7 @@ function IsometricCanvas({
|
|
|
11494
11699
|
};
|
|
11495
11700
|
}, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
|
|
11496
11701
|
React82.useEffect(() => {
|
|
11497
|
-
const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0;
|
|
11702
|
+
const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0 || atlasPending > 0;
|
|
11498
11703
|
draw(animTimeRef.current);
|
|
11499
11704
|
if (!hasAnimations) return;
|
|
11500
11705
|
let running = true;
|
|
@@ -11510,7 +11715,7 @@ function IsometricCanvas({
|
|
|
11510
11715
|
running = false;
|
|
11511
11716
|
cancelAnimationFrame(rafIdRef.current);
|
|
11512
11717
|
};
|
|
11513
|
-
}, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId, hasActiveEffects2, pendingCount, lerpToTarget, targetCameraRef]);
|
|
11718
|
+
}, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId, hasActiveEffects2, pendingCount, atlasPending, lerpToTarget, targetCameraRef]);
|
|
11514
11719
|
const handleMouseMoveWithCamera = React82.useCallback((e) => {
|
|
11515
11720
|
if (enableCamera) {
|
|
11516
11721
|
const wasPanning = handleMouseMove(e, () => draw(animTimeRef.current));
|
|
@@ -11655,6 +11860,7 @@ var init_IsometricCanvas = __esm({
|
|
|
11655
11860
|
init_ErrorState();
|
|
11656
11861
|
init_useImageCache();
|
|
11657
11862
|
init_useCamera();
|
|
11863
|
+
init_useUnitSpriteAtlas();
|
|
11658
11864
|
init_verificationRegistry();
|
|
11659
11865
|
init_isometric();
|
|
11660
11866
|
IsometricCanvas.displayName = "IsometricCanvas";
|
|
@@ -29237,13 +29443,13 @@ var init_MapView = __esm({
|
|
|
29237
29443
|
shadowSize: [41, 41]
|
|
29238
29444
|
});
|
|
29239
29445
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
29240
|
-
const { useEffect:
|
|
29446
|
+
const { useEffect: useEffect79, useRef: useRef75, useCallback: useCallback118, useState: useState109 } = React82__namespace.default;
|
|
29241
29447
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
29242
29448
|
const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
29243
29449
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
29244
29450
|
const map = useMap();
|
|
29245
|
-
const prevRef =
|
|
29246
|
-
|
|
29451
|
+
const prevRef = useRef75({ centerLat, centerLng, zoom });
|
|
29452
|
+
useEffect79(() => {
|
|
29247
29453
|
const prev = prevRef.current;
|
|
29248
29454
|
if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
|
|
29249
29455
|
map.setView([centerLat, centerLng], zoom);
|
|
@@ -29254,7 +29460,7 @@ var init_MapView = __esm({
|
|
|
29254
29460
|
}
|
|
29255
29461
|
function MapClickHandler({ onMapClick }) {
|
|
29256
29462
|
const map = useMap();
|
|
29257
|
-
|
|
29463
|
+
useEffect79(() => {
|
|
29258
29464
|
if (!onMapClick) return;
|
|
29259
29465
|
const handler = (e) => {
|
|
29260
29466
|
onMapClick(e.latlng.lat, e.latlng.lng);
|
|
@@ -29282,8 +29488,8 @@ var init_MapView = __esm({
|
|
|
29282
29488
|
showAttribution = true
|
|
29283
29489
|
}) {
|
|
29284
29490
|
const eventBus = useEventBus3();
|
|
29285
|
-
const [clickedPosition, setClickedPosition] =
|
|
29286
|
-
const handleMapClick =
|
|
29491
|
+
const [clickedPosition, setClickedPosition] = useState109(null);
|
|
29492
|
+
const handleMapClick = useCallback118((lat, lng) => {
|
|
29287
29493
|
if (showClickedPin) {
|
|
29288
29494
|
setClickedPosition({ lat, lng });
|
|
29289
29495
|
}
|
|
@@ -29292,7 +29498,7 @@ var init_MapView = __esm({
|
|
|
29292
29498
|
eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
|
|
29293
29499
|
}
|
|
29294
29500
|
}, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
|
|
29295
|
-
const handleMarkerClick =
|
|
29501
|
+
const handleMarkerClick = useCallback118((marker) => {
|
|
29296
29502
|
onMarkerClick?.(marker);
|
|
29297
29503
|
if (markerClickEvent) {
|
|
29298
29504
|
eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
|
|
@@ -41010,7 +41216,7 @@ var init_AssetLoader = __esm({
|
|
|
41010
41216
|
__publicField(this, "textureCache");
|
|
41011
41217
|
__publicField(this, "loadingPromises");
|
|
41012
41218
|
this.objLoader = new OBJLoader_js.OBJLoader();
|
|
41013
|
-
this.textureLoader = new
|
|
41219
|
+
this.textureLoader = new THREE3__namespace.TextureLoader();
|
|
41014
41220
|
this.modelCache = /* @__PURE__ */ new Map();
|
|
41015
41221
|
this.textureCache = /* @__PURE__ */ new Map();
|
|
41016
41222
|
this.loadingPromises = /* @__PURE__ */ new Map();
|
|
@@ -41084,7 +41290,7 @@ var init_AssetLoader = __esm({
|
|
|
41084
41290
|
return this.loadingPromises.get(`texture:${url}`);
|
|
41085
41291
|
}
|
|
41086
41292
|
const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
|
|
41087
|
-
texture.colorSpace =
|
|
41293
|
+
texture.colorSpace = THREE3__namespace.SRGBColorSpace;
|
|
41088
41294
|
this.textureCache.set(url, texture);
|
|
41089
41295
|
this.loadingPromises.delete(`texture:${url}`);
|
|
41090
41296
|
return texture;
|
|
@@ -41158,7 +41364,7 @@ var init_AssetLoader = __esm({
|
|
|
41158
41364
|
});
|
|
41159
41365
|
this.modelCache.forEach((model) => {
|
|
41160
41366
|
model.scene.traverse((child) => {
|
|
41161
|
-
if (child instanceof
|
|
41367
|
+
if (child instanceof THREE3__namespace.Mesh) {
|
|
41162
41368
|
child.geometry.dispose();
|
|
41163
41369
|
if (Array.isArray(child.material)) {
|
|
41164
41370
|
child.material.forEach((m) => m.dispose());
|
|
@@ -41714,7 +41920,7 @@ function ModelLoader({
|
|
|
41714
41920
|
if (!loadedModel) return null;
|
|
41715
41921
|
const cloned = loadedModel.clone();
|
|
41716
41922
|
cloned.traverse((child) => {
|
|
41717
|
-
if (child instanceof
|
|
41923
|
+
if (child instanceof THREE3__namespace.Mesh) {
|
|
41718
41924
|
child.castShadow = castShadow;
|
|
41719
41925
|
child.receiveShadow = receiveShadow;
|
|
41720
41926
|
}
|
|
@@ -41828,6 +42034,47 @@ function CameraController({
|
|
|
41828
42034
|
}, [camera.position, onCameraChange]);
|
|
41829
42035
|
return null;
|
|
41830
42036
|
}
|
|
42037
|
+
function UnitSpriteBillboard({
|
|
42038
|
+
sheetUrl,
|
|
42039
|
+
resolveFrame,
|
|
42040
|
+
height = 1.2
|
|
42041
|
+
}) {
|
|
42042
|
+
const texture = fiber.useLoader(THREE3__namespace.TextureLoader, sheetUrl);
|
|
42043
|
+
const meshRef = React82.useRef(null);
|
|
42044
|
+
const matRef = React82.useRef(null);
|
|
42045
|
+
const [aspect, setAspect] = React82.useState(1);
|
|
42046
|
+
fiber.useFrame(() => {
|
|
42047
|
+
const frame = resolveFrame();
|
|
42048
|
+
if (!frame || !texture.image) return;
|
|
42049
|
+
const imgW = texture.image.width;
|
|
42050
|
+
const imgH = texture.image.height;
|
|
42051
|
+
if (!imgW || !imgH) return;
|
|
42052
|
+
texture.repeat.set((frame.flipX ? -1 : 1) * (frame.sw / imgW), frame.sh / imgH);
|
|
42053
|
+
texture.offset.set(
|
|
42054
|
+
frame.flipX ? (frame.sx + frame.sw) / imgW : frame.sx / imgW,
|
|
42055
|
+
1 - (frame.sy + frame.sh) / imgH
|
|
42056
|
+
);
|
|
42057
|
+
texture.magFilter = THREE3__namespace.NearestFilter;
|
|
42058
|
+
texture.minFilter = THREE3__namespace.NearestFilter;
|
|
42059
|
+
texture.needsUpdate = true;
|
|
42060
|
+
const nextAspect = frame.sw / frame.sh;
|
|
42061
|
+
if (Math.abs(nextAspect - aspect) > 1e-3) setAspect(nextAspect);
|
|
42062
|
+
if (matRef.current) matRef.current.needsUpdate = true;
|
|
42063
|
+
});
|
|
42064
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ref: meshRef, position: [0, height / 2, 0], children: [
|
|
42065
|
+
/* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [height * aspect, height] }),
|
|
42066
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
42067
|
+
"meshBasicMaterial",
|
|
42068
|
+
{
|
|
42069
|
+
ref: matRef,
|
|
42070
|
+
map: texture,
|
|
42071
|
+
transparent: true,
|
|
42072
|
+
alphaTest: 0.1,
|
|
42073
|
+
side: THREE3__namespace.DoubleSide
|
|
42074
|
+
}
|
|
42075
|
+
)
|
|
42076
|
+
] });
|
|
42077
|
+
}
|
|
41831
42078
|
var DEFAULT_GRID_CONFIG, GameCanvas3D;
|
|
41832
42079
|
var init_GameCanvas3D2 = __esm({
|
|
41833
42080
|
"components/game/molecules/GameCanvas3D.tsx"() {
|
|
@@ -41838,6 +42085,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41838
42085
|
init_Canvas3DLoadingState2();
|
|
41839
42086
|
init_Canvas3DErrorBoundary2();
|
|
41840
42087
|
init_ModelLoader();
|
|
42088
|
+
init_useUnitSpriteAtlas();
|
|
41841
42089
|
init_cn();
|
|
41842
42090
|
init_GameCanvas3D();
|
|
41843
42091
|
DEFAULT_GRID_CONFIG = {
|
|
@@ -41894,8 +42142,10 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41894
42142
|
const controlsRef = React82.useRef(null);
|
|
41895
42143
|
const [hoveredTile, setHoveredTile] = React82.useState(null);
|
|
41896
42144
|
const [internalError, setInternalError] = React82.useState(null);
|
|
42145
|
+
const { sheetUrls: atlasSheetUrls, resolveUnitFrame } = useUnitSpriteAtlas(units);
|
|
42146
|
+
const preloadUrls = React82.useMemo(() => [...preloadAssets, ...atlasSheetUrls], [preloadAssets, atlasSheetUrls]);
|
|
41897
42147
|
const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
|
|
41898
|
-
preloadUrls
|
|
42148
|
+
preloadUrls,
|
|
41899
42149
|
loader: customAssetLoader
|
|
41900
42150
|
});
|
|
41901
42151
|
const eventHandlers = useGameCanvas3DEvents({
|
|
@@ -41954,7 +42204,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41954
42204
|
getCameraPosition: () => {
|
|
41955
42205
|
if (controlsRef.current) {
|
|
41956
42206
|
const pos = controlsRef.current.object.position;
|
|
41957
|
-
return new
|
|
42207
|
+
return new THREE3__namespace.Vector3(pos.x, pos.y, pos.z);
|
|
41958
42208
|
}
|
|
41959
42209
|
return null;
|
|
41960
42210
|
},
|
|
@@ -42106,6 +42356,8 @@ var init_GameCanvas3D2 = __esm({
|
|
|
42106
42356
|
({ unit, position }) => {
|
|
42107
42357
|
const isSelected = selectedUnitId === unit.id;
|
|
42108
42358
|
const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
|
|
42359
|
+
const hasAtlas = unitAtlasUrl(unit) !== null;
|
|
42360
|
+
const initialFrame = hasAtlas ? resolveUnitFrame(unit.id) : null;
|
|
42109
42361
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
42110
42362
|
"group",
|
|
42111
42363
|
{
|
|
@@ -42117,7 +42369,16 @@ var init_GameCanvas3D2 = __esm({
|
|
|
42117
42369
|
/* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
|
|
42118
42370
|
/* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
|
|
42119
42371
|
] }),
|
|
42120
|
-
|
|
42372
|
+
hasAtlas && initialFrame ? (
|
|
42373
|
+
/* Animated sprite-sheet billboard — single cropped frame, by state */
|
|
42374
|
+
/* @__PURE__ */ jsxRuntime.jsx(drei.Billboard, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
42375
|
+
UnitSpriteBillboard,
|
|
42376
|
+
{
|
|
42377
|
+
sheetUrl: initialFrame.sheetUrl,
|
|
42378
|
+
resolveFrame: () => resolveUnitFrame(unit.id)
|
|
42379
|
+
}
|
|
42380
|
+
) })
|
|
42381
|
+
) : unit.modelUrl ? (
|
|
42121
42382
|
/* GLB unit model (box fallback while loading / on error) */
|
|
42122
42383
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
42123
42384
|
ModelLoader,
|
|
@@ -42171,7 +42432,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
42171
42432
|
}
|
|
42172
42433
|
);
|
|
42173
42434
|
},
|
|
42174
|
-
[selectedUnitId, handleUnitClick]
|
|
42435
|
+
[selectedUnitId, handleUnitClick, resolveUnitFrame]
|
|
42175
42436
|
);
|
|
42176
42437
|
const DefaultFeatureRenderer = React82.useCallback(
|
|
42177
42438
|
({
|