@almadar/ui 5.71.0 → 5.73.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 +2178 -2309
- package/dist/avl/index.js +912 -1043
- package/dist/components/game/atoms/ActionButton.d.ts +3 -3
- package/dist/components/game/atoms/ChoiceButton.d.ts +3 -3
- package/dist/components/game/atoms/ComboCounter.d.ts +3 -3
- package/dist/components/game/atoms/ControlButton.d.ts +3 -3
- package/dist/components/game/atoms/DamageNumber.d.ts +3 -3
- package/dist/components/game/atoms/DialogueBubble.d.ts +3 -3
- package/dist/components/game/atoms/ItemSlot.d.ts +3 -3
- package/dist/components/game/atoms/ResourceCounter.d.ts +3 -3
- package/dist/components/game/atoms/ScoreDisplay.d.ts +4 -6
- package/dist/components/game/atoms/Sprite.d.ts +9 -21
- package/dist/components/game/atoms/StateIndicator.d.ts +2 -2
- package/dist/components/game/atoms/StatusEffect.d.ts +3 -3
- package/dist/components/game/atoms/TurnIndicator.d.ts +3 -3
- package/dist/components/game/atoms/WaypointMarker.d.ts +3 -3
- package/dist/components/game/atoms/XPBar.d.ts +1 -1
- package/dist/components/game/molecules/CardHand.d.ts +2 -2
- package/dist/components/game/molecules/DialogueBox.d.ts +3 -3
- package/dist/components/game/molecules/EnemyPlate.d.ts +3 -3
- package/dist/components/game/molecules/GameCanvas2D.d.ts +3 -3
- package/dist/components/game/molecules/GameCanvas3D.d.ts +0 -2
- package/dist/components/game/molecules/HealthPanel.d.ts +3 -3
- package/dist/components/game/molecules/IsometricCanvas.d.ts +11 -13
- package/dist/components/game/molecules/PlatformerCanvas.d.ts +4 -1
- package/dist/components/game/molecules/PowerupSlots.d.ts +3 -3
- package/dist/components/game/molecules/StatBadge.d.ts +6 -4
- package/dist/components/game/molecules/TurnPanel.d.ts +3 -3
- package/dist/components/game/molecules/three/index.cjs +55 -109
- package/dist/components/game/molecules/three/index.js +55 -109
- package/dist/components/game/molecules/useUnitSpriteAtlas.d.ts +6 -12
- package/dist/components/game/organisms/BattleBoard.d.ts +5 -6
- package/dist/components/game/organisms/CardBattlerBoard.d.ts +3 -4
- package/dist/components/game/organisms/CastleBoard.d.ts +5 -6
- package/dist/components/game/organisms/CityBuilderBoard.d.ts +6 -7
- package/dist/components/game/organisms/FishingBoard.d.ts +4 -6
- package/dist/components/game/organisms/PlatformerBoard.d.ts +5 -5
- package/dist/components/game/organisms/RoguelikeBoard.d.ts +4 -5
- package/dist/components/game/organisms/TopDownShooterBoard.d.ts +5 -6
- package/dist/components/game/organisms/TowerDefenseBoard.d.ts +5 -6
- package/dist/components/game/organisms/VisualNovelBoard.d.ts +4 -5
- package/dist/components/game/organisms/WorldMapBoard.d.ts +5 -6
- package/dist/components/game/organisms/types/effects.d.ts +7 -8
- package/dist/components/game/organisms/types/isometric.d.ts +24 -8
- package/dist/components/game/organisms/types/spriteAnimation.d.ts +3 -4
- package/dist/components/game/organisms/utils/makeAsset.d.ts +5 -0
- package/dist/components/game/templates/PlatformerTemplate.d.ts +5 -4
- package/dist/components/game/templates/game3dAssetManifest.d.ts +6 -8
- package/dist/components/index.cjs +2314 -2411
- package/dist/components/index.js +1203 -1300
- package/dist/providers/index.cjs +1987 -2118
- package/dist/providers/index.js +888 -1019
- package/dist/runtime/index.cjs +2043 -2174
- package/dist/runtime/index.js +892 -1023
- package/package.json +2 -2
|
@@ -1779,7 +1779,7 @@ function TileRenderer({
|
|
|
1779
1779
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1780
1780
|
ModelLoader,
|
|
1781
1781
|
{
|
|
1782
|
-
url: tile.modelUrl,
|
|
1782
|
+
url: tile.modelUrl?.url,
|
|
1783
1783
|
position,
|
|
1784
1784
|
scale: cellSize * 0.9,
|
|
1785
1785
|
fallbackGeometry: "box",
|
|
@@ -2181,7 +2181,7 @@ function FeatureModel({
|
|
|
2181
2181
|
onHover
|
|
2182
2182
|
}) {
|
|
2183
2183
|
const groupRef = React11.useRef(null);
|
|
2184
|
-
const { model: loadedModel, isLoading } = useGLTFModel2(feature.assetUrl);
|
|
2184
|
+
const { model: loadedModel, isLoading } = useGLTFModel2(feature.assetUrl?.url);
|
|
2185
2185
|
const model = React11.useMemo(() => {
|
|
2186
2186
|
if (!loadedModel) return null;
|
|
2187
2187
|
const cloned = loadedModel.clone();
|
|
@@ -2293,13 +2293,6 @@ function preloadFeatures(urls) {
|
|
|
2293
2293
|
}
|
|
2294
2294
|
|
|
2295
2295
|
// components/game/organisms/utils/spriteAnimation.ts
|
|
2296
|
-
function inferDirection(dx, dy) {
|
|
2297
|
-
if (dx === 0 && dy === 0) return "se";
|
|
2298
|
-
if (dx >= 0 && dy >= 0) return "se";
|
|
2299
|
-
if (dx <= 0 && dy >= 0) return "sw";
|
|
2300
|
-
if (dx >= 0 && dy <= 0) return "ne";
|
|
2301
|
-
return "nw";
|
|
2302
|
-
}
|
|
2303
2296
|
function resolveSheetDirection(facing) {
|
|
2304
2297
|
switch (facing) {
|
|
2305
2298
|
case "se":
|
|
@@ -2320,29 +2313,10 @@ function frameRect(frame, row, columns, frameWidth, frameHeight) {
|
|
|
2320
2313
|
sh: frameHeight
|
|
2321
2314
|
};
|
|
2322
2315
|
}
|
|
2323
|
-
function getCurrentFrameFromDef(def, elapsed) {
|
|
2324
|
-
const frameDuration = 1e3 / def.frameRate;
|
|
2325
|
-
const totalDuration = def.frames * frameDuration;
|
|
2326
|
-
if (def.loop) {
|
|
2327
|
-
const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
|
|
2328
|
-
return { frame: frame2, finished: false };
|
|
2329
|
-
}
|
|
2330
|
-
if (elapsed >= totalDuration) {
|
|
2331
|
-
return { frame: def.frames - 1, finished: true };
|
|
2332
|
-
}
|
|
2333
|
-
const frame = Math.floor(elapsed / frameDuration);
|
|
2334
|
-
return { frame, finished: false };
|
|
2335
|
-
}
|
|
2336
2316
|
|
|
2337
2317
|
// components/game/molecules/useUnitSpriteAtlas.ts
|
|
2338
|
-
var WALK_HOLD_MS = 600;
|
|
2339
2318
|
function unitAtlasUrl(unit) {
|
|
2340
|
-
|
|
2341
|
-
const sprite = unit.sprite;
|
|
2342
|
-
if (!sprite) return null;
|
|
2343
|
-
const match = /^(.*-sprite-sheet)(?:-(?:se|sw))?(?:-v\d+)?\.png$/.exec(sprite);
|
|
2344
|
-
if (!match) return null;
|
|
2345
|
-
return `${match[1]}.json`;
|
|
2319
|
+
return unit.spriteSheet?.url ?? null;
|
|
2346
2320
|
}
|
|
2347
2321
|
function resolveSheetUrl(atlasUrl, relativeSheetPath) {
|
|
2348
2322
|
try {
|
|
@@ -2357,9 +2331,6 @@ function useUnitSpriteAtlas(units) {
|
|
|
2357
2331
|
const loadingRef = React11.useRef(/* @__PURE__ */ new Set());
|
|
2358
2332
|
const [pendingCount, setPendingCount] = React11.useState(0);
|
|
2359
2333
|
const [, forceTick] = React11.useState(0);
|
|
2360
|
-
const animStatesRef = React11.useRef(/* @__PURE__ */ new Map());
|
|
2361
|
-
const lastTickRef = React11.useRef(0);
|
|
2362
|
-
const rafRef = React11.useRef(0);
|
|
2363
2334
|
const atlasUrls = React11.useMemo(() => {
|
|
2364
2335
|
const set = /* @__PURE__ */ new Set();
|
|
2365
2336
|
for (const unit of units) {
|
|
@@ -2405,54 +2376,6 @@ function useUnitSpriteAtlas(units) {
|
|
|
2405
2376
|
}
|
|
2406
2377
|
return [...urls];
|
|
2407
2378
|
}, [units, pendingCount]);
|
|
2408
|
-
React11.useEffect(() => {
|
|
2409
|
-
const hasAtlasUnits = units.some((u) => unitAtlasUrl(u) !== null);
|
|
2410
|
-
if (!hasAtlasUnits) return;
|
|
2411
|
-
let running = true;
|
|
2412
|
-
const tick = (ts) => {
|
|
2413
|
-
if (!running) return;
|
|
2414
|
-
const last = lastTickRef.current || ts;
|
|
2415
|
-
const delta = ts - last;
|
|
2416
|
-
lastTickRef.current = ts;
|
|
2417
|
-
const states = animStatesRef.current;
|
|
2418
|
-
const currentIds = /* @__PURE__ */ new Set();
|
|
2419
|
-
for (const unit of units) {
|
|
2420
|
-
if (unitAtlasUrl(unit) === null) continue;
|
|
2421
|
-
currentIds.add(unit.id);
|
|
2422
|
-
let state = states.get(unit.id);
|
|
2423
|
-
if (!state) {
|
|
2424
|
-
state = { animation: "idle", direction: "se", elapsed: 0, walkHold: 0, prev: null };
|
|
2425
|
-
states.set(unit.id, state);
|
|
2426
|
-
}
|
|
2427
|
-
const posX = unit.position?.x ?? unit.x ?? 0;
|
|
2428
|
-
const posY = unit.position?.y ?? unit.y ?? 0;
|
|
2429
|
-
if (state.prev) {
|
|
2430
|
-
const dx = posX - state.prev.x;
|
|
2431
|
-
const dy = posY - state.prev.y;
|
|
2432
|
-
if (dx !== 0 || dy !== 0) {
|
|
2433
|
-
state.animation = "walk";
|
|
2434
|
-
state.direction = inferDirection(dx, dy);
|
|
2435
|
-
state.walkHold = WALK_HOLD_MS;
|
|
2436
|
-
} else if (state.animation === "walk") {
|
|
2437
|
-
state.walkHold -= delta;
|
|
2438
|
-
if (state.walkHold <= 0) state.animation = "idle";
|
|
2439
|
-
}
|
|
2440
|
-
}
|
|
2441
|
-
state.prev = { x: posX, y: posY };
|
|
2442
|
-
state.elapsed += delta;
|
|
2443
|
-
}
|
|
2444
|
-
for (const id of states.keys()) {
|
|
2445
|
-
if (!currentIds.has(id)) states.delete(id);
|
|
2446
|
-
}
|
|
2447
|
-
rafRef.current = requestAnimationFrame(tick);
|
|
2448
|
-
};
|
|
2449
|
-
rafRef.current = requestAnimationFrame(tick);
|
|
2450
|
-
return () => {
|
|
2451
|
-
running = false;
|
|
2452
|
-
cancelAnimationFrame(rafRef.current);
|
|
2453
|
-
lastTickRef.current = 0;
|
|
2454
|
-
};
|
|
2455
|
-
}, [units]);
|
|
2456
2379
|
const resolveUnitFrame = React11.useCallback((unitId) => {
|
|
2457
2380
|
const unit = units.find((u) => u.id === unitId);
|
|
2458
2381
|
if (!unit) return null;
|
|
@@ -2460,18 +2383,14 @@ function useUnitSpriteAtlas(units) {
|
|
|
2460
2383
|
if (!atlasUrl) return null;
|
|
2461
2384
|
const atlas = atlasCacheRef.current.get(atlasUrl);
|
|
2462
2385
|
if (!atlas) return null;
|
|
2463
|
-
const
|
|
2464
|
-
const
|
|
2465
|
-
const direction = state?.direction ?? "se";
|
|
2466
|
-
const elapsed = state?.elapsed ?? 0;
|
|
2386
|
+
const animation = unit.animation ?? "idle";
|
|
2387
|
+
const frame = unit.frame ?? 0;
|
|
2467
2388
|
const def = atlas.animations[animation] ?? atlas.animations.idle;
|
|
2468
2389
|
if (!def) return null;
|
|
2469
|
-
const { sheetDir, flipX } = resolveSheetDirection(
|
|
2390
|
+
const { sheetDir, flipX } = resolveSheetDirection("se");
|
|
2470
2391
|
const rel = atlas.sheets[sheetDir] ?? atlas.sheets.se ?? atlas.sheets.sw;
|
|
2471
2392
|
if (!rel) return null;
|
|
2472
2393
|
const sheetUrl = resolveSheetUrl(atlasUrl, rel);
|
|
2473
|
-
const isIdle = animation === "idle";
|
|
2474
|
-
const frame = isIdle ? 0 : getCurrentFrameFromDef(def, elapsed).frame;
|
|
2475
2394
|
const rect = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
|
|
2476
2395
|
return {
|
|
2477
2396
|
sheetUrl,
|
|
@@ -2480,7 +2399,7 @@ function useUnitSpriteAtlas(units) {
|
|
|
2480
2399
|
sw: rect.sw,
|
|
2481
2400
|
sh: rect.sh,
|
|
2482
2401
|
flipX,
|
|
2483
|
-
applyBreathing:
|
|
2402
|
+
applyBreathing: animation === "idle"
|
|
2484
2403
|
};
|
|
2485
2404
|
}, [units]);
|
|
2486
2405
|
return { sheetUrls, resolveUnitFrame, pendingCount };
|
|
@@ -2576,7 +2495,6 @@ var GameCanvas3D = React11.forwardRef(
|
|
|
2576
2495
|
className,
|
|
2577
2496
|
isLoading: externalLoading,
|
|
2578
2497
|
error: externalError,
|
|
2579
|
-
entity,
|
|
2580
2498
|
preloadAssets = [],
|
|
2581
2499
|
tileClickEvent,
|
|
2582
2500
|
unitClickEvent,
|
|
@@ -2809,7 +2727,7 @@ var GameCanvas3D = React11.forwardRef(
|
|
|
2809
2727
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2810
2728
|
ModelLoader,
|
|
2811
2729
|
{
|
|
2812
|
-
url: tile.modelUrl,
|
|
2730
|
+
url: tile.modelUrl.url,
|
|
2813
2731
|
scale: 0.95,
|
|
2814
2732
|
fallbackGeometry: "box",
|
|
2815
2733
|
castShadow: true,
|
|
@@ -2874,7 +2792,7 @@ var GameCanvas3D = React11.forwardRef(
|
|
|
2874
2792
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2875
2793
|
ModelLoader,
|
|
2876
2794
|
{
|
|
2877
|
-
url: unit.modelUrl,
|
|
2795
|
+
url: unit.modelUrl.url,
|
|
2878
2796
|
scale: modelScale,
|
|
2879
2797
|
fallbackGeometry: "box",
|
|
2880
2798
|
castShadow: true
|
|
@@ -2934,7 +2852,7 @@ var GameCanvas3D = React11.forwardRef(
|
|
|
2934
2852
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2935
2853
|
ModelLoader,
|
|
2936
2854
|
{
|
|
2937
|
-
url: feature.assetUrl,
|
|
2855
|
+
url: feature.assetUrl.url,
|
|
2938
2856
|
position,
|
|
2939
2857
|
scale: 0.5,
|
|
2940
2858
|
rotation: [0, feature.rotation ?? 0, 0],
|
|
@@ -4302,7 +4220,18 @@ function GameBoard3D({
|
|
|
4302
4220
|
const row = boardEntity(entity);
|
|
4303
4221
|
const eventBus = useEventBus();
|
|
4304
4222
|
const entityUnits = row ? rows(row.units) : [];
|
|
4305
|
-
const units = entityUnits
|
|
4223
|
+
const units = entityUnits.map((r) => ({
|
|
4224
|
+
id: str(r.id),
|
|
4225
|
+
x: num(r.x),
|
|
4226
|
+
y: num(r.y),
|
|
4227
|
+
z: num(r.z),
|
|
4228
|
+
faction: str(r.faction) || "neutral",
|
|
4229
|
+
team: str(r.team) || str(r.faction) || "neutral",
|
|
4230
|
+
unitType: r.unitType == null ? void 0 : str(r.unitType),
|
|
4231
|
+
name: r.name == null ? void 0 : str(r.name),
|
|
4232
|
+
health: r.health == null ? void 0 : num(r.health),
|
|
4233
|
+
maxHealth: r.maxHealth == null ? void 0 : num(r.maxHealth)
|
|
4234
|
+
}));
|
|
4306
4235
|
const selectedUnitId = row ? str(row.selectedUnitId) || null : null;
|
|
4307
4236
|
const phase = row ? str(row.phase) : "observation";
|
|
4308
4237
|
const result = row ? str(row.result) : "none";
|
|
@@ -4694,16 +4623,31 @@ var Box = React11__default.default.forwardRef(
|
|
|
4694
4623
|
);
|
|
4695
4624
|
Box.displayName = "Box";
|
|
4696
4625
|
|
|
4697
|
-
// components/game/
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4626
|
+
// components/game/organisms/utils/makeAsset.ts
|
|
4627
|
+
var ROLE_DEFAULTS = {
|
|
4628
|
+
tile: { dimension: "2d", animations: ["static"], aspect: "1:1" },
|
|
4629
|
+
decoration: { dimension: "2d", animations: ["static"], aspect: "1:1" },
|
|
4630
|
+
effect: { dimension: "2d", animations: ["static"], aspect: "1:1" },
|
|
4631
|
+
item: { dimension: "2d", animations: ["static"], aspect: "5:7" },
|
|
4632
|
+
player: { dimension: "2d", animations: ["idle", "walk", "attack", "hit", "death"], aspect: "1:1" },
|
|
4633
|
+
enemy: { dimension: "2d", animations: ["idle", "walk", "attack", "hit", "death"], aspect: "1:1" },
|
|
4634
|
+
npc: { dimension: "2d", animations: ["idle", "walk", "attack", "hit", "death"], aspect: "1:1" },
|
|
4635
|
+
projectile: { dimension: "2d", animations: ["static"], aspect: "1:1" },
|
|
4636
|
+
vehicle: { dimension: "2d", animations: ["idle", "walk"], aspect: "1:1" },
|
|
4637
|
+
ui: { dimension: "2d", animations: ["static"], aspect: "1:1" }
|
|
4638
|
+
};
|
|
4639
|
+
function makeAsset(url, role, overrides = {}) {
|
|
4640
|
+
const defaults = ROLE_DEFAULTS[role] ?? { dimension: "2d", animations: ["static"], aspect: "1:1" };
|
|
4641
|
+
return {
|
|
4642
|
+
url,
|
|
4643
|
+
role,
|
|
4644
|
+
category: url.split("/").pop()?.replace(/\.[^.]+$/, "") ?? role,
|
|
4645
|
+
...defaults,
|
|
4646
|
+
...overrides
|
|
4647
|
+
};
|
|
4706
4648
|
}
|
|
4649
|
+
|
|
4650
|
+
// components/game/templates/game3dAssetManifest.ts
|
|
4707
4651
|
function tileKind(type, passable) {
|
|
4708
4652
|
if (passable === false) return "wall";
|
|
4709
4653
|
if (type === "dirt" || type === "road") return "dirt";
|
|
@@ -4719,7 +4663,7 @@ function resolveTilesWithModels(layout, manifest) {
|
|
|
4719
4663
|
z: t.z,
|
|
4720
4664
|
type: t.type,
|
|
4721
4665
|
passable: t.passable,
|
|
4722
|
-
modelUrl:
|
|
4666
|
+
modelUrl: manifest?.models?.[kind]
|
|
4723
4667
|
};
|
|
4724
4668
|
});
|
|
4725
4669
|
}
|
|
@@ -4728,7 +4672,7 @@ function resolvePropTiles(tiles, manifest) {
|
|
|
4728
4672
|
const kind = tileKind(t.type ?? "", t.passable);
|
|
4729
4673
|
return {
|
|
4730
4674
|
...t,
|
|
4731
|
-
modelUrl: t.modelUrl ??
|
|
4675
|
+
modelUrl: t.modelUrl ?? manifest?.models?.[kind]
|
|
4732
4676
|
};
|
|
4733
4677
|
});
|
|
4734
4678
|
}
|
|
@@ -4739,7 +4683,8 @@ function resolveEntityTiles(entity, manifest) {
|
|
|
4739
4683
|
const type = str(r.type) || str(r.terrain);
|
|
4740
4684
|
const passable = r.passable !== false;
|
|
4741
4685
|
const kind = tileKind(type, passable);
|
|
4742
|
-
const
|
|
4686
|
+
const explicitModelUrl = r.modelUrl == null ? void 0 : str(r.modelUrl);
|
|
4687
|
+
const explicitModel = explicitModelUrl ? makeAsset(explicitModelUrl, "decoration", { dimension: "3d" }) : void 0;
|
|
4743
4688
|
return {
|
|
4744
4689
|
id: r.id == null ? void 0 : str(r.id),
|
|
4745
4690
|
x: num(r.x),
|
|
@@ -4747,14 +4692,14 @@ function resolveEntityTiles(entity, manifest) {
|
|
|
4747
4692
|
z: r.z == null ? void 0 : num(r.z),
|
|
4748
4693
|
type,
|
|
4749
4694
|
passable,
|
|
4750
|
-
modelUrl: explicitModel ??
|
|
4695
|
+
modelUrl: explicitModel ?? manifest?.models?.[kind]
|
|
4751
4696
|
};
|
|
4752
4697
|
});
|
|
4753
4698
|
}
|
|
4754
4699
|
function resolveFeaturesWithModels(features, manifest) {
|
|
4755
4700
|
return features.map((f) => ({
|
|
4756
4701
|
...f,
|
|
4757
|
-
assetUrl: f.assetUrl ??
|
|
4702
|
+
assetUrl: f.assetUrl ?? manifest?.features?.[f.type]
|
|
4758
4703
|
}));
|
|
4759
4704
|
}
|
|
4760
4705
|
function resolveEntityFeatures(entity, manifest) {
|
|
@@ -4762,7 +4707,8 @@ function resolveEntityFeatures(entity, manifest) {
|
|
|
4762
4707
|
if (!row) return [];
|
|
4763
4708
|
return rows(row.features).map((r) => {
|
|
4764
4709
|
const type = str(r.type);
|
|
4765
|
-
const
|
|
4710
|
+
const explicitUrl = r.assetUrl == null ? void 0 : str(r.assetUrl);
|
|
4711
|
+
const explicit = explicitUrl ? makeAsset(explicitUrl, "decoration", { dimension: "3d" }) : void 0;
|
|
4766
4712
|
return {
|
|
4767
4713
|
id: r.id == null ? void 0 : str(r.id),
|
|
4768
4714
|
x: num(r.x),
|
|
@@ -4770,7 +4716,7 @@ function resolveEntityFeatures(entity, manifest) {
|
|
|
4770
4716
|
z: r.z == null ? void 0 : num(r.z),
|
|
4771
4717
|
type,
|
|
4772
4718
|
color: r.color == null ? void 0 : str(r.color),
|
|
4773
|
-
assetUrl: explicit ??
|
|
4719
|
+
assetUrl: explicit ?? manifest?.features?.[type]
|
|
4774
4720
|
};
|
|
4775
4721
|
});
|
|
4776
4722
|
}
|
|
@@ -1755,7 +1755,7 @@ function TileRenderer({
|
|
|
1755
1755
|
children: /* @__PURE__ */ jsx(
|
|
1756
1756
|
ModelLoader,
|
|
1757
1757
|
{
|
|
1758
|
-
url: tile.modelUrl,
|
|
1758
|
+
url: tile.modelUrl?.url,
|
|
1759
1759
|
position,
|
|
1760
1760
|
scale: cellSize * 0.9,
|
|
1761
1761
|
fallbackGeometry: "box",
|
|
@@ -2157,7 +2157,7 @@ function FeatureModel({
|
|
|
2157
2157
|
onHover
|
|
2158
2158
|
}) {
|
|
2159
2159
|
const groupRef = useRef(null);
|
|
2160
|
-
const { model: loadedModel, isLoading } = useGLTFModel2(feature.assetUrl);
|
|
2160
|
+
const { model: loadedModel, isLoading } = useGLTFModel2(feature.assetUrl?.url);
|
|
2161
2161
|
const model = useMemo(() => {
|
|
2162
2162
|
if (!loadedModel) return null;
|
|
2163
2163
|
const cloned = loadedModel.clone();
|
|
@@ -2269,13 +2269,6 @@ function preloadFeatures(urls) {
|
|
|
2269
2269
|
}
|
|
2270
2270
|
|
|
2271
2271
|
// components/game/organisms/utils/spriteAnimation.ts
|
|
2272
|
-
function inferDirection(dx, dy) {
|
|
2273
|
-
if (dx === 0 && dy === 0) return "se";
|
|
2274
|
-
if (dx >= 0 && dy >= 0) return "se";
|
|
2275
|
-
if (dx <= 0 && dy >= 0) return "sw";
|
|
2276
|
-
if (dx >= 0 && dy <= 0) return "ne";
|
|
2277
|
-
return "nw";
|
|
2278
|
-
}
|
|
2279
2272
|
function resolveSheetDirection(facing) {
|
|
2280
2273
|
switch (facing) {
|
|
2281
2274
|
case "se":
|
|
@@ -2296,29 +2289,10 @@ function frameRect(frame, row, columns, frameWidth, frameHeight) {
|
|
|
2296
2289
|
sh: frameHeight
|
|
2297
2290
|
};
|
|
2298
2291
|
}
|
|
2299
|
-
function getCurrentFrameFromDef(def, elapsed) {
|
|
2300
|
-
const frameDuration = 1e3 / def.frameRate;
|
|
2301
|
-
const totalDuration = def.frames * frameDuration;
|
|
2302
|
-
if (def.loop) {
|
|
2303
|
-
const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
|
|
2304
|
-
return { frame: frame2, finished: false };
|
|
2305
|
-
}
|
|
2306
|
-
if (elapsed >= totalDuration) {
|
|
2307
|
-
return { frame: def.frames - 1, finished: true };
|
|
2308
|
-
}
|
|
2309
|
-
const frame = Math.floor(elapsed / frameDuration);
|
|
2310
|
-
return { frame, finished: false };
|
|
2311
|
-
}
|
|
2312
2292
|
|
|
2313
2293
|
// components/game/molecules/useUnitSpriteAtlas.ts
|
|
2314
|
-
var WALK_HOLD_MS = 600;
|
|
2315
2294
|
function unitAtlasUrl(unit) {
|
|
2316
|
-
|
|
2317
|
-
const sprite = unit.sprite;
|
|
2318
|
-
if (!sprite) return null;
|
|
2319
|
-
const match = /^(.*-sprite-sheet)(?:-(?:se|sw))?(?:-v\d+)?\.png$/.exec(sprite);
|
|
2320
|
-
if (!match) return null;
|
|
2321
|
-
return `${match[1]}.json`;
|
|
2295
|
+
return unit.spriteSheet?.url ?? null;
|
|
2322
2296
|
}
|
|
2323
2297
|
function resolveSheetUrl(atlasUrl, relativeSheetPath) {
|
|
2324
2298
|
try {
|
|
@@ -2333,9 +2307,6 @@ function useUnitSpriteAtlas(units) {
|
|
|
2333
2307
|
const loadingRef = useRef(/* @__PURE__ */ new Set());
|
|
2334
2308
|
const [pendingCount, setPendingCount] = useState(0);
|
|
2335
2309
|
const [, forceTick] = useState(0);
|
|
2336
|
-
const animStatesRef = useRef(/* @__PURE__ */ new Map());
|
|
2337
|
-
const lastTickRef = useRef(0);
|
|
2338
|
-
const rafRef = useRef(0);
|
|
2339
2310
|
const atlasUrls = useMemo(() => {
|
|
2340
2311
|
const set = /* @__PURE__ */ new Set();
|
|
2341
2312
|
for (const unit of units) {
|
|
@@ -2381,54 +2352,6 @@ function useUnitSpriteAtlas(units) {
|
|
|
2381
2352
|
}
|
|
2382
2353
|
return [...urls];
|
|
2383
2354
|
}, [units, pendingCount]);
|
|
2384
|
-
useEffect(() => {
|
|
2385
|
-
const hasAtlasUnits = units.some((u) => unitAtlasUrl(u) !== null);
|
|
2386
|
-
if (!hasAtlasUnits) return;
|
|
2387
|
-
let running = true;
|
|
2388
|
-
const tick = (ts) => {
|
|
2389
|
-
if (!running) return;
|
|
2390
|
-
const last = lastTickRef.current || ts;
|
|
2391
|
-
const delta = ts - last;
|
|
2392
|
-
lastTickRef.current = ts;
|
|
2393
|
-
const states = animStatesRef.current;
|
|
2394
|
-
const currentIds = /* @__PURE__ */ new Set();
|
|
2395
|
-
for (const unit of units) {
|
|
2396
|
-
if (unitAtlasUrl(unit) === null) continue;
|
|
2397
|
-
currentIds.add(unit.id);
|
|
2398
|
-
let state = states.get(unit.id);
|
|
2399
|
-
if (!state) {
|
|
2400
|
-
state = { animation: "idle", direction: "se", elapsed: 0, walkHold: 0, prev: null };
|
|
2401
|
-
states.set(unit.id, state);
|
|
2402
|
-
}
|
|
2403
|
-
const posX = unit.position?.x ?? unit.x ?? 0;
|
|
2404
|
-
const posY = unit.position?.y ?? unit.y ?? 0;
|
|
2405
|
-
if (state.prev) {
|
|
2406
|
-
const dx = posX - state.prev.x;
|
|
2407
|
-
const dy = posY - state.prev.y;
|
|
2408
|
-
if (dx !== 0 || dy !== 0) {
|
|
2409
|
-
state.animation = "walk";
|
|
2410
|
-
state.direction = inferDirection(dx, dy);
|
|
2411
|
-
state.walkHold = WALK_HOLD_MS;
|
|
2412
|
-
} else if (state.animation === "walk") {
|
|
2413
|
-
state.walkHold -= delta;
|
|
2414
|
-
if (state.walkHold <= 0) state.animation = "idle";
|
|
2415
|
-
}
|
|
2416
|
-
}
|
|
2417
|
-
state.prev = { x: posX, y: posY };
|
|
2418
|
-
state.elapsed += delta;
|
|
2419
|
-
}
|
|
2420
|
-
for (const id of states.keys()) {
|
|
2421
|
-
if (!currentIds.has(id)) states.delete(id);
|
|
2422
|
-
}
|
|
2423
|
-
rafRef.current = requestAnimationFrame(tick);
|
|
2424
|
-
};
|
|
2425
|
-
rafRef.current = requestAnimationFrame(tick);
|
|
2426
|
-
return () => {
|
|
2427
|
-
running = false;
|
|
2428
|
-
cancelAnimationFrame(rafRef.current);
|
|
2429
|
-
lastTickRef.current = 0;
|
|
2430
|
-
};
|
|
2431
|
-
}, [units]);
|
|
2432
2355
|
const resolveUnitFrame = useCallback((unitId) => {
|
|
2433
2356
|
const unit = units.find((u) => u.id === unitId);
|
|
2434
2357
|
if (!unit) return null;
|
|
@@ -2436,18 +2359,14 @@ function useUnitSpriteAtlas(units) {
|
|
|
2436
2359
|
if (!atlasUrl) return null;
|
|
2437
2360
|
const atlas = atlasCacheRef.current.get(atlasUrl);
|
|
2438
2361
|
if (!atlas) return null;
|
|
2439
|
-
const
|
|
2440
|
-
const
|
|
2441
|
-
const direction = state?.direction ?? "se";
|
|
2442
|
-
const elapsed = state?.elapsed ?? 0;
|
|
2362
|
+
const animation = unit.animation ?? "idle";
|
|
2363
|
+
const frame = unit.frame ?? 0;
|
|
2443
2364
|
const def = atlas.animations[animation] ?? atlas.animations.idle;
|
|
2444
2365
|
if (!def) return null;
|
|
2445
|
-
const { sheetDir, flipX } = resolveSheetDirection(
|
|
2366
|
+
const { sheetDir, flipX } = resolveSheetDirection("se");
|
|
2446
2367
|
const rel = atlas.sheets[sheetDir] ?? atlas.sheets.se ?? atlas.sheets.sw;
|
|
2447
2368
|
if (!rel) return null;
|
|
2448
2369
|
const sheetUrl = resolveSheetUrl(atlasUrl, rel);
|
|
2449
|
-
const isIdle = animation === "idle";
|
|
2450
|
-
const frame = isIdle ? 0 : getCurrentFrameFromDef(def, elapsed).frame;
|
|
2451
2370
|
const rect = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
|
|
2452
2371
|
return {
|
|
2453
2372
|
sheetUrl,
|
|
@@ -2456,7 +2375,7 @@ function useUnitSpriteAtlas(units) {
|
|
|
2456
2375
|
sw: rect.sw,
|
|
2457
2376
|
sh: rect.sh,
|
|
2458
2377
|
flipX,
|
|
2459
|
-
applyBreathing:
|
|
2378
|
+
applyBreathing: animation === "idle"
|
|
2460
2379
|
};
|
|
2461
2380
|
}, [units]);
|
|
2462
2381
|
return { sheetUrls, resolveUnitFrame, pendingCount };
|
|
@@ -2552,7 +2471,6 @@ var GameCanvas3D = forwardRef(
|
|
|
2552
2471
|
className,
|
|
2553
2472
|
isLoading: externalLoading,
|
|
2554
2473
|
error: externalError,
|
|
2555
|
-
entity,
|
|
2556
2474
|
preloadAssets = [],
|
|
2557
2475
|
tileClickEvent,
|
|
2558
2476
|
unitClickEvent,
|
|
@@ -2785,7 +2703,7 @@ var GameCanvas3D = forwardRef(
|
|
|
2785
2703
|
children: /* @__PURE__ */ jsx(
|
|
2786
2704
|
ModelLoader,
|
|
2787
2705
|
{
|
|
2788
|
-
url: tile.modelUrl,
|
|
2706
|
+
url: tile.modelUrl.url,
|
|
2789
2707
|
scale: 0.95,
|
|
2790
2708
|
fallbackGeometry: "box",
|
|
2791
2709
|
castShadow: true,
|
|
@@ -2850,7 +2768,7 @@ var GameCanvas3D = forwardRef(
|
|
|
2850
2768
|
/* @__PURE__ */ jsx(
|
|
2851
2769
|
ModelLoader,
|
|
2852
2770
|
{
|
|
2853
|
-
url: unit.modelUrl,
|
|
2771
|
+
url: unit.modelUrl.url,
|
|
2854
2772
|
scale: modelScale,
|
|
2855
2773
|
fallbackGeometry: "box",
|
|
2856
2774
|
castShadow: true
|
|
@@ -2910,7 +2828,7 @@ var GameCanvas3D = forwardRef(
|
|
|
2910
2828
|
return /* @__PURE__ */ jsx(
|
|
2911
2829
|
ModelLoader,
|
|
2912
2830
|
{
|
|
2913
|
-
url: feature.assetUrl,
|
|
2831
|
+
url: feature.assetUrl.url,
|
|
2914
2832
|
position,
|
|
2915
2833
|
scale: 0.5,
|
|
2916
2834
|
rotation: [0, feature.rotation ?? 0, 0],
|
|
@@ -4278,7 +4196,18 @@ function GameBoard3D({
|
|
|
4278
4196
|
const row = boardEntity(entity);
|
|
4279
4197
|
const eventBus = useEventBus();
|
|
4280
4198
|
const entityUnits = row ? rows(row.units) : [];
|
|
4281
|
-
const units = entityUnits
|
|
4199
|
+
const units = entityUnits.map((r) => ({
|
|
4200
|
+
id: str(r.id),
|
|
4201
|
+
x: num(r.x),
|
|
4202
|
+
y: num(r.y),
|
|
4203
|
+
z: num(r.z),
|
|
4204
|
+
faction: str(r.faction) || "neutral",
|
|
4205
|
+
team: str(r.team) || str(r.faction) || "neutral",
|
|
4206
|
+
unitType: r.unitType == null ? void 0 : str(r.unitType),
|
|
4207
|
+
name: r.name == null ? void 0 : str(r.name),
|
|
4208
|
+
health: r.health == null ? void 0 : num(r.health),
|
|
4209
|
+
maxHealth: r.maxHealth == null ? void 0 : num(r.maxHealth)
|
|
4210
|
+
}));
|
|
4282
4211
|
const selectedUnitId = row ? str(row.selectedUnitId) || null : null;
|
|
4283
4212
|
const phase = row ? str(row.phase) : "observation";
|
|
4284
4213
|
const result = row ? str(row.result) : "none";
|
|
@@ -4670,16 +4599,31 @@ var Box = React11.forwardRef(
|
|
|
4670
4599
|
);
|
|
4671
4600
|
Box.displayName = "Box";
|
|
4672
4601
|
|
|
4673
|
-
// components/game/
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4602
|
+
// components/game/organisms/utils/makeAsset.ts
|
|
4603
|
+
var ROLE_DEFAULTS = {
|
|
4604
|
+
tile: { dimension: "2d", animations: ["static"], aspect: "1:1" },
|
|
4605
|
+
decoration: { dimension: "2d", animations: ["static"], aspect: "1:1" },
|
|
4606
|
+
effect: { dimension: "2d", animations: ["static"], aspect: "1:1" },
|
|
4607
|
+
item: { dimension: "2d", animations: ["static"], aspect: "5:7" },
|
|
4608
|
+
player: { dimension: "2d", animations: ["idle", "walk", "attack", "hit", "death"], aspect: "1:1" },
|
|
4609
|
+
enemy: { dimension: "2d", animations: ["idle", "walk", "attack", "hit", "death"], aspect: "1:1" },
|
|
4610
|
+
npc: { dimension: "2d", animations: ["idle", "walk", "attack", "hit", "death"], aspect: "1:1" },
|
|
4611
|
+
projectile: { dimension: "2d", animations: ["static"], aspect: "1:1" },
|
|
4612
|
+
vehicle: { dimension: "2d", animations: ["idle", "walk"], aspect: "1:1" },
|
|
4613
|
+
ui: { dimension: "2d", animations: ["static"], aspect: "1:1" }
|
|
4614
|
+
};
|
|
4615
|
+
function makeAsset(url, role, overrides = {}) {
|
|
4616
|
+
const defaults = ROLE_DEFAULTS[role] ?? { dimension: "2d", animations: ["static"], aspect: "1:1" };
|
|
4617
|
+
return {
|
|
4618
|
+
url,
|
|
4619
|
+
role,
|
|
4620
|
+
category: url.split("/").pop()?.replace(/\.[^.]+$/, "") ?? role,
|
|
4621
|
+
...defaults,
|
|
4622
|
+
...overrides
|
|
4623
|
+
};
|
|
4682
4624
|
}
|
|
4625
|
+
|
|
4626
|
+
// components/game/templates/game3dAssetManifest.ts
|
|
4683
4627
|
function tileKind(type, passable) {
|
|
4684
4628
|
if (passable === false) return "wall";
|
|
4685
4629
|
if (type === "dirt" || type === "road") return "dirt";
|
|
@@ -4695,7 +4639,7 @@ function resolveTilesWithModels(layout, manifest) {
|
|
|
4695
4639
|
z: t.z,
|
|
4696
4640
|
type: t.type,
|
|
4697
4641
|
passable: t.passable,
|
|
4698
|
-
modelUrl:
|
|
4642
|
+
modelUrl: manifest?.models?.[kind]
|
|
4699
4643
|
};
|
|
4700
4644
|
});
|
|
4701
4645
|
}
|
|
@@ -4704,7 +4648,7 @@ function resolvePropTiles(tiles, manifest) {
|
|
|
4704
4648
|
const kind = tileKind(t.type ?? "", t.passable);
|
|
4705
4649
|
return {
|
|
4706
4650
|
...t,
|
|
4707
|
-
modelUrl: t.modelUrl ??
|
|
4651
|
+
modelUrl: t.modelUrl ?? manifest?.models?.[kind]
|
|
4708
4652
|
};
|
|
4709
4653
|
});
|
|
4710
4654
|
}
|
|
@@ -4715,7 +4659,8 @@ function resolveEntityTiles(entity, manifest) {
|
|
|
4715
4659
|
const type = str(r.type) || str(r.terrain);
|
|
4716
4660
|
const passable = r.passable !== false;
|
|
4717
4661
|
const kind = tileKind(type, passable);
|
|
4718
|
-
const
|
|
4662
|
+
const explicitModelUrl = r.modelUrl == null ? void 0 : str(r.modelUrl);
|
|
4663
|
+
const explicitModel = explicitModelUrl ? makeAsset(explicitModelUrl, "decoration", { dimension: "3d" }) : void 0;
|
|
4719
4664
|
return {
|
|
4720
4665
|
id: r.id == null ? void 0 : str(r.id),
|
|
4721
4666
|
x: num(r.x),
|
|
@@ -4723,14 +4668,14 @@ function resolveEntityTiles(entity, manifest) {
|
|
|
4723
4668
|
z: r.z == null ? void 0 : num(r.z),
|
|
4724
4669
|
type,
|
|
4725
4670
|
passable,
|
|
4726
|
-
modelUrl: explicitModel ??
|
|
4671
|
+
modelUrl: explicitModel ?? manifest?.models?.[kind]
|
|
4727
4672
|
};
|
|
4728
4673
|
});
|
|
4729
4674
|
}
|
|
4730
4675
|
function resolveFeaturesWithModels(features, manifest) {
|
|
4731
4676
|
return features.map((f) => ({
|
|
4732
4677
|
...f,
|
|
4733
|
-
assetUrl: f.assetUrl ??
|
|
4678
|
+
assetUrl: f.assetUrl ?? manifest?.features?.[f.type]
|
|
4734
4679
|
}));
|
|
4735
4680
|
}
|
|
4736
4681
|
function resolveEntityFeatures(entity, manifest) {
|
|
@@ -4738,7 +4683,8 @@ function resolveEntityFeatures(entity, manifest) {
|
|
|
4738
4683
|
if (!row) return [];
|
|
4739
4684
|
return rows(row.features).map((r) => {
|
|
4740
4685
|
const type = str(r.type);
|
|
4741
|
-
const
|
|
4686
|
+
const explicitUrl = r.assetUrl == null ? void 0 : str(r.assetUrl);
|
|
4687
|
+
const explicit = explicitUrl ? makeAsset(explicitUrl, "decoration", { dimension: "3d" }) : void 0;
|
|
4742
4688
|
return {
|
|
4743
4689
|
id: r.id == null ? void 0 : str(r.id),
|
|
4744
4690
|
x: num(r.x),
|
|
@@ -4746,7 +4692,7 @@ function resolveEntityFeatures(entity, manifest) {
|
|
|
4746
4692
|
z: r.z == null ? void 0 : num(r.z),
|
|
4747
4693
|
type,
|
|
4748
4694
|
color: r.color == null ? void 0 : str(r.color),
|
|
4749
|
-
assetUrl: explicit ??
|
|
4695
|
+
assetUrl: explicit ?? manifest?.features?.[type]
|
|
4750
4696
|
};
|
|
4751
4697
|
});
|
|
4752
4698
|
}
|
|
@@ -1,19 +1,12 @@
|
|
|
1
|
-
import type { AssetUrl } from '@almadar/core';
|
|
2
1
|
import type { IsometricUnit } from '../organisms/types/isometric';
|
|
3
2
|
import type { ResolvedFrame } from '../organisms/types/spriteAnimation';
|
|
4
3
|
/**
|
|
5
4
|
* Resolve the sprite-sheet atlas-JSON URL for a unit.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* the deterministic naming convention used in `shared/sprite-sheets/`:
|
|
10
|
-
* the shared atlas drops the `-se` / `-sw` direction suffix and any `-vN`
|
|
11
|
-
* variant suffix, then swaps `.png` → `.json`
|
|
12
|
-
* (`amir-sprite-sheet-se.png` → `amir-sprite-sheet.json`).
|
|
13
|
-
*
|
|
14
|
-
* Returns null when no atlas can be resolved (the unit keeps its static draw).
|
|
6
|
+
* A unit has an atlas iff `unit.spriteSheet.url` is set (explicit metadata wins).
|
|
7
|
+
* No filename-pattern matching — a unit without a `spriteSheet` Asset keeps its static draw.
|
|
15
8
|
*/
|
|
16
|
-
export declare function unitAtlasUrl(unit: Pick<IsometricUnit, 'spriteSheet'
|
|
9
|
+
export declare function unitAtlasUrl(unit: Pick<IsometricUnit, 'spriteSheet'>): string | null;
|
|
17
10
|
export interface UseUnitSpriteAtlasResult {
|
|
18
11
|
/** Atlas-JSON URLs successfully loaded — feed these to no one; sheet PNGs are returned below. */
|
|
19
12
|
sheetUrls: string[];
|
|
@@ -23,8 +16,9 @@ export interface UseUnitSpriteAtlasResult {
|
|
|
23
16
|
pendingCount: number;
|
|
24
17
|
}
|
|
25
18
|
/**
|
|
26
|
-
* Load per-unit sprite atlases and
|
|
19
|
+
* Load per-unit sprite atlases and expose a pure frame resolver.
|
|
20
|
+
* Frame is computed from each unit's own `animation` + `frame` props — no internal clock.
|
|
27
21
|
*
|
|
28
|
-
* @param units - Units
|
|
22
|
+
* @param units - Units whose atlases should be loaded.
|
|
29
23
|
*/
|
|
30
24
|
export declare function useUnitSpriteAtlas(units: IsometricUnit[]): UseUnitSpriteAtlasResult;
|