@almadar/ui 1.0.21 → 1.0.22
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/{chunk-I5RSZIOE.js → chunk-4UFNDD6B.js} +82 -26
- package/dist/{chunk-VE4ZELYZ.js → chunk-ITRZURGQ.js} +3 -115
- package/dist/chunk-W5YTXLXL.js +29 -0
- package/dist/{chunk-TTXKOHDO.js → chunk-WXFQV3ZP.js} +148 -2
- package/dist/components/index.d.ts +724 -278
- package/dist/components/index.js +1549 -901
- package/dist/context/index.js +14 -3
- package/dist/hooks/index.d.ts +124 -122
- package/dist/hooks/index.js +3 -3
- package/dist/providers/index.d.ts +0 -1
- package/dist/providers/index.js +14 -5
- package/package.json +10 -5
- package/themes/arctic.css +203 -0
- package/themes/copper.css +203 -0
- package/themes/ember.css +206 -0
- package/themes/forest.css +206 -0
- package/themes/index.css +15 -0
- package/themes/lavender.css +201 -0
- package/themes/midnight.css +202 -0
- package/themes/neon.css +208 -0
- package/themes/ocean.css +206 -0
- package/themes/rose.css +201 -0
- package/themes/sand.css +202 -0
- package/themes/slate.css +201 -0
- package/themes/sunset.css +206 -0
- package/dist/chunk-4FRUCUO5.js +0 -14
package/dist/components/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useQuerySingleton,
|
|
3
|
-
export { ENTITY_EVENTS,
|
|
1
|
+
import { useUISlots } from '../chunk-W5YTXLXL.js';
|
|
2
|
+
import { useQuerySingleton, useSelectedEntity, useAuthContext } from '../chunk-ITRZURGQ.js';
|
|
3
|
+
export { ENTITY_EVENTS, parseQueryBinding, useAgentChat, useAuthContext, useCompile, useConnectGitHub, useCreateEntity, useDeepAgentGeneration, useDeleteEntity, useDisconnectGitHub, useEntities, useEntitiesByType, useEntity as useEntityById, useEntityMutations, useExtensions, useFileEditor, useFileSystem, useGitHubBranches, useGitHubRepo, useGitHubRepos, useGitHubStatus, useInput, useOrbitalHistory, useOrbitalMutations, usePhysics, usePlayer, usePreview, useQuerySingleton, useSelectedEntity, useSendOrbitalEvent, useSingletonEntity, useUIEvents, useUpdateEntity, useValidation } from '../chunk-ITRZURGQ.js';
|
|
4
|
+
export { DEFAULT_SLOTS, useUISlotManager } from '../chunk-7NEWMNNU.js';
|
|
4
5
|
import { cn, debugGroup, debug, debugGroupEnd, getNestedValue, isDebugEnabled } from '../chunk-KKCVDUK7.js';
|
|
5
6
|
export { cn } from '../chunk-KKCVDUK7.js';
|
|
6
7
|
import '../chunk-XSEDIUM6.js';
|
|
7
|
-
import { useTheme
|
|
8
|
-
import { useEventBus } from '../chunk-
|
|
9
|
-
export { useEmitEvent, useEventBus, useEventListener } from '../chunk-
|
|
10
|
-
export { DEFAULT_SLOTS, useUISlotManager } from '../chunk-7NEWMNNU.js';
|
|
8
|
+
import { useTheme } from '../chunk-4UFNDD6B.js';
|
|
9
|
+
import { useEventBus, usePaginatedEntityList, useEntityList, useEntityDetail } from '../chunk-WXFQV3ZP.js';
|
|
10
|
+
export { EntityDataProvider, entityDataKeys, useEmitEvent, useEntity, useEntityDataAdapter, useEntityDetail, useEntityList, useEventBus, useEventListener } from '../chunk-WXFQV3ZP.js';
|
|
11
11
|
export { clearEntities, getAllEntities, getByType, getEntity, getSingleton, removeEntity, spawnEntity, updateEntity, updateSingleton } from '../chunk-N7MVUW4R.js';
|
|
12
|
-
import '../chunk-PKBMQBKP.js';
|
|
12
|
+
import { __publicField } from '../chunk-PKBMQBKP.js';
|
|
13
13
|
import * as React37 from 'react';
|
|
14
14
|
import React37__default, { useCallback, useMemo, useState, useEffect, useRef } from 'react';
|
|
15
15
|
import * as LucideIcons from 'lucide-react';
|
|
@@ -1816,47 +1816,95 @@ ThemeToggle.displayName = "ThemeToggle";
|
|
|
1816
1816
|
var THEME_LABELS = {
|
|
1817
1817
|
wireframe: {
|
|
1818
1818
|
label: "Wireframe",
|
|
1819
|
-
|
|
1820
|
-
description: "Sharp corners, thick borders"
|
|
1819
|
+
description: "Sharp corners, thick borders, brutalist"
|
|
1821
1820
|
},
|
|
1822
1821
|
minimalist: {
|
|
1823
1822
|
label: "Minimalist",
|
|
1824
|
-
icon: "\u2728",
|
|
1825
1823
|
description: "Clean, subtle, refined"
|
|
1826
1824
|
},
|
|
1827
1825
|
almadar: {
|
|
1828
1826
|
label: "Almadar",
|
|
1829
|
-
icon: "\u{1F48E}",
|
|
1830
1827
|
description: "Teal gradients, glowing accents"
|
|
1828
|
+
},
|
|
1829
|
+
"trait-wars": {
|
|
1830
|
+
label: "Trait Wars",
|
|
1831
|
+
description: "Gold parchment, game manuscript"
|
|
1832
|
+
},
|
|
1833
|
+
ocean: {
|
|
1834
|
+
label: "Ocean",
|
|
1835
|
+
description: "Deep sea calm, ocean blues"
|
|
1836
|
+
},
|
|
1837
|
+
forest: {
|
|
1838
|
+
label: "Forest",
|
|
1839
|
+
description: "Woodland serenity, earthy greens"
|
|
1840
|
+
},
|
|
1841
|
+
sunset: {
|
|
1842
|
+
label: "Sunset",
|
|
1843
|
+
description: "Golden hour, warm coral and amber"
|
|
1844
|
+
},
|
|
1845
|
+
lavender: {
|
|
1846
|
+
label: "Lavender",
|
|
1847
|
+
description: "Creative studio, soft violet"
|
|
1848
|
+
},
|
|
1849
|
+
rose: {
|
|
1850
|
+
label: "Rose",
|
|
1851
|
+
description: "Elegant bloom, warm pink"
|
|
1852
|
+
},
|
|
1853
|
+
slate: {
|
|
1854
|
+
label: "Slate",
|
|
1855
|
+
description: "Corporate edge, cool gray"
|
|
1856
|
+
},
|
|
1857
|
+
ember: {
|
|
1858
|
+
label: "Ember",
|
|
1859
|
+
description: "Fire and energy, bold red"
|
|
1860
|
+
},
|
|
1861
|
+
midnight: {
|
|
1862
|
+
label: "Midnight",
|
|
1863
|
+
description: "Noir elegance, deep indigo"
|
|
1864
|
+
},
|
|
1865
|
+
sand: {
|
|
1866
|
+
label: "Sand",
|
|
1867
|
+
description: "Desert minimal, warm earth"
|
|
1868
|
+
},
|
|
1869
|
+
neon: {
|
|
1870
|
+
label: "Neon",
|
|
1871
|
+
description: "Cyberpunk, glowing cyan and pink"
|
|
1872
|
+
},
|
|
1873
|
+
arctic: {
|
|
1874
|
+
label: "Arctic",
|
|
1875
|
+
description: "Ice crystal, cool blue"
|
|
1876
|
+
},
|
|
1877
|
+
copper: {
|
|
1878
|
+
label: "Copper",
|
|
1879
|
+
description: "Warm industrial, metallic bronze"
|
|
1831
1880
|
}
|
|
1832
1881
|
};
|
|
1882
|
+
function getThemeLabel(name) {
|
|
1883
|
+
return THEME_LABELS[name] || { label: name, description: name };
|
|
1884
|
+
}
|
|
1833
1885
|
var ThemeSelector = ({
|
|
1834
1886
|
className = "",
|
|
1835
1887
|
variant = "dropdown",
|
|
1836
1888
|
showLabels = true
|
|
1837
1889
|
}) => {
|
|
1838
|
-
const {
|
|
1890
|
+
const { theme, setTheme, availableThemes } = useTheme();
|
|
1839
1891
|
if (variant === "buttons") {
|
|
1840
|
-
return /* @__PURE__ */ jsx("div", { className: `flex gap-2 ${className}`, children: availableThemes.map((
|
|
1841
|
-
const { label
|
|
1842
|
-
const isActive =
|
|
1843
|
-
return /* @__PURE__ */
|
|
1892
|
+
return /* @__PURE__ */ jsx("div", { className: `flex gap-2 flex-wrap ${className}`, children: availableThemes.map((t) => {
|
|
1893
|
+
const { label } = getThemeLabel(t.name);
|
|
1894
|
+
const isActive = theme === t.name;
|
|
1895
|
+
return /* @__PURE__ */ jsx(
|
|
1844
1896
|
"button",
|
|
1845
1897
|
{
|
|
1846
|
-
onClick: () =>
|
|
1898
|
+
onClick: () => setTheme(t.name),
|
|
1847
1899
|
className: `
|
|
1848
1900
|
px-3 py-2 text-sm font-medium transition-all
|
|
1849
1901
|
border-[length:var(--border-width)] rounded-[var(--radius-sm)]
|
|
1850
1902
|
${isActive ? "bg-[var(--color-primary)] text-[var(--color-primary-foreground)] border-[var(--color-primary)]" : "bg-[var(--color-secondary)] text-[var(--color-secondary-foreground)] border-[var(--color-border)] hover:bg-[var(--color-secondary-hover)]"}
|
|
1851
1903
|
`,
|
|
1852
|
-
title:
|
|
1853
|
-
children:
|
|
1854
|
-
icon,
|
|
1855
|
-
" ",
|
|
1856
|
-
showLabels && label
|
|
1857
|
-
]
|
|
1904
|
+
title: getThemeLabel(t.name).description,
|
|
1905
|
+
children: showLabels && label
|
|
1858
1906
|
},
|
|
1859
|
-
|
|
1907
|
+
t.name
|
|
1860
1908
|
);
|
|
1861
1909
|
}) });
|
|
1862
1910
|
}
|
|
@@ -1864,8 +1912,8 @@ var ThemeSelector = ({
|
|
|
1864
1912
|
/* @__PURE__ */ jsx(
|
|
1865
1913
|
"select",
|
|
1866
1914
|
{
|
|
1867
|
-
value:
|
|
1868
|
-
onChange: (e) =>
|
|
1915
|
+
value: theme,
|
|
1916
|
+
onChange: (e) => setTheme(e.target.value),
|
|
1869
1917
|
className: `
|
|
1870
1918
|
px-3 py-2 pr-8 text-sm font-medium
|
|
1871
1919
|
bg-[var(--color-secondary)] text-[var(--color-secondary-foreground)]
|
|
@@ -1874,13 +1922,9 @@ var ThemeSelector = ({
|
|
|
1874
1922
|
cursor-pointer appearance-none
|
|
1875
1923
|
focus:outline-none focus:ring-2 focus:ring-[var(--color-ring)]
|
|
1876
1924
|
`,
|
|
1877
|
-
children: availableThemes.map((
|
|
1878
|
-
const { label
|
|
1879
|
-
return /* @__PURE__ */
|
|
1880
|
-
icon,
|
|
1881
|
-
" ",
|
|
1882
|
-
label
|
|
1883
|
-
] }, theme);
|
|
1925
|
+
children: availableThemes.map((t) => {
|
|
1926
|
+
const { label } = getThemeLabel(t.name);
|
|
1927
|
+
return /* @__PURE__ */ jsx("option", { value: t.name, children: label }, t.name);
|
|
1884
1928
|
})
|
|
1885
1929
|
}
|
|
1886
1930
|
),
|
|
@@ -10163,8 +10207,9 @@ function useCamera() {
|
|
|
10163
10207
|
|
|
10164
10208
|
// components/organisms/game/utils/isometric.ts
|
|
10165
10209
|
var TILE_WIDTH = 256;
|
|
10166
|
-
var TILE_HEIGHT =
|
|
10167
|
-
var FLOOR_HEIGHT =
|
|
10210
|
+
var TILE_HEIGHT = 512;
|
|
10211
|
+
var FLOOR_HEIGHT = 128;
|
|
10212
|
+
var DIAMOND_TOP_Y = 374;
|
|
10168
10213
|
var FEATURE_COLORS = {
|
|
10169
10214
|
goldMine: "#fbbf24",
|
|
10170
10215
|
resonanceCrystal: "#a78bfa",
|
|
@@ -10230,6 +10275,8 @@ function IsometricCanvas({
|
|
|
10230
10275
|
effectSpriteUrls = [],
|
|
10231
10276
|
onDrawEffects,
|
|
10232
10277
|
hasActiveEffects: hasActiveEffects2 = false,
|
|
10278
|
+
// Tuning
|
|
10279
|
+
diamondTopY: diamondTopYProp,
|
|
10233
10280
|
// Remote asset loading
|
|
10234
10281
|
assetBaseUrl,
|
|
10235
10282
|
assetManifest
|
|
@@ -10276,6 +10323,8 @@ function IsometricCanvas({
|
|
|
10276
10323
|
const scaledTileWidth = TILE_WIDTH * scale;
|
|
10277
10324
|
const scaledTileHeight = TILE_HEIGHT * scale;
|
|
10278
10325
|
const scaledFloorHeight = FLOOR_HEIGHT * scale;
|
|
10326
|
+
const effectiveDiamondTopY = diamondTopYProp ?? DIAMOND_TOP_Y;
|
|
10327
|
+
const scaledDiamondTopY = effectiveDiamondTopY * scale;
|
|
10279
10328
|
const baseOffsetX = useMemo(() => {
|
|
10280
10329
|
return (gridHeight - 1) * (scaledTileWidth / 2);
|
|
10281
10330
|
}, [gridHeight, scaledTileWidth]);
|
|
@@ -10295,10 +10344,10 @@ function IsometricCanvas({
|
|
|
10295
10344
|
for (const tile of sortedTiles) {
|
|
10296
10345
|
if (tile.terrainSprite) urls.push(tile.terrainSprite);
|
|
10297
10346
|
else if (getTerrainSprite) {
|
|
10298
|
-
const url = getTerrainSprite(tile.terrain);
|
|
10347
|
+
const url = getTerrainSprite(tile.terrain ?? "");
|
|
10299
10348
|
if (url) urls.push(url);
|
|
10300
10349
|
} else {
|
|
10301
|
-
const url = resolveManifestUrl(assetManifest?.terrains?.[tile.terrain]);
|
|
10350
|
+
const url = resolveManifestUrl(assetManifest?.terrains?.[tile.terrain ?? ""]);
|
|
10302
10351
|
if (url) urls.push(url);
|
|
10303
10352
|
}
|
|
10304
10353
|
}
|
|
@@ -10347,7 +10396,7 @@ function IsometricCanvas({
|
|
|
10347
10396
|
lerpToTarget
|
|
10348
10397
|
} = useCamera();
|
|
10349
10398
|
const resolveTerrainSpriteUrl = useCallback((tile) => {
|
|
10350
|
-
return tile.terrainSprite || getTerrainSprite?.(tile.terrain) || resolveManifestUrl(assetManifest?.terrains?.[tile.terrain]);
|
|
10399
|
+
return tile.terrainSprite || getTerrainSprite?.(tile.terrain ?? "") || resolveManifestUrl(assetManifest?.terrains?.[tile.terrain ?? ""]);
|
|
10351
10400
|
}, [getTerrainSprite, assetManifest, resolveManifestUrl]);
|
|
10352
10401
|
const resolveFeatureSpriteUrl = useCallback((featureType) => {
|
|
10353
10402
|
return getFeatureSprite?.(featureType) || resolveManifestUrl(assetManifest?.features?.[featureType]);
|
|
@@ -10392,6 +10441,7 @@ function IsometricCanvas({
|
|
|
10392
10441
|
mCtx.fill();
|
|
10393
10442
|
}
|
|
10394
10443
|
for (const unit of units) {
|
|
10444
|
+
if (!unit.position) continue;
|
|
10395
10445
|
const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
|
|
10396
10446
|
const mx = (pos.x + scaledTileWidth / 2 - minX) * scaleM + offsetMx;
|
|
10397
10447
|
const my = (pos.y + scaledTileHeight / 2 - minY) * scaleM + offsetMy;
|
|
@@ -10458,7 +10508,7 @@ function IsometricCanvas({
|
|
|
10458
10508
|
ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
|
|
10459
10509
|
} else {
|
|
10460
10510
|
const centerX = pos.x + scaledTileWidth / 2;
|
|
10461
|
-
const topY = pos.y +
|
|
10511
|
+
const topY = pos.y + scaledDiamondTopY;
|
|
10462
10512
|
ctx.fillStyle = tile.terrain === "water" ? "#3b82f6" : tile.terrain === "mountain" ? "#78716c" : tile.terrain === "stone" ? "#9ca3af" : "#4ade80";
|
|
10463
10513
|
ctx.beginPath();
|
|
10464
10514
|
ctx.moveTo(centerX, topY);
|
|
@@ -10473,7 +10523,7 @@ function IsometricCanvas({
|
|
|
10473
10523
|
}
|
|
10474
10524
|
if (hoveredTile && hoveredTile.x === tile.x && hoveredTile.y === tile.y) {
|
|
10475
10525
|
const centerX = pos.x + scaledTileWidth / 2;
|
|
10476
|
-
const topY = pos.y +
|
|
10526
|
+
const topY = pos.y + scaledDiamondTopY;
|
|
10477
10527
|
ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
|
|
10478
10528
|
ctx.beginPath();
|
|
10479
10529
|
ctx.moveTo(centerX, topY);
|
|
@@ -10486,7 +10536,7 @@ function IsometricCanvas({
|
|
|
10486
10536
|
const tileKey = `${tile.x},${tile.y}`;
|
|
10487
10537
|
if (validMoveSet.has(tileKey)) {
|
|
10488
10538
|
const centerX = pos.x + scaledTileWidth / 2;
|
|
10489
|
-
const topY = pos.y +
|
|
10539
|
+
const topY = pos.y + scaledDiamondTopY;
|
|
10490
10540
|
const pulse = 0.15 + 0.1 * Math.sin(animTime * 4e-3);
|
|
10491
10541
|
ctx.fillStyle = `rgba(74, 222, 128, ${pulse})`;
|
|
10492
10542
|
ctx.beginPath();
|
|
@@ -10499,7 +10549,7 @@ function IsometricCanvas({
|
|
|
10499
10549
|
}
|
|
10500
10550
|
if (attackTargetSet.has(tileKey)) {
|
|
10501
10551
|
const centerX = pos.x + scaledTileWidth / 2;
|
|
10502
|
-
const topY = pos.y +
|
|
10552
|
+
const topY = pos.y + scaledDiamondTopY;
|
|
10503
10553
|
const pulse = 0.2 + 0.15 * Math.sin(animTime * 5e-3);
|
|
10504
10554
|
ctx.fillStyle = `rgba(239, 68, 68, ${pulse})`;
|
|
10505
10555
|
ctx.beginPath();
|
|
@@ -10512,7 +10562,7 @@ function IsometricCanvas({
|
|
|
10512
10562
|
}
|
|
10513
10563
|
if (debug2) {
|
|
10514
10564
|
const centerX = pos.x + scaledTileWidth / 2;
|
|
10515
|
-
const centerY = pos.y + scaledFloorHeight / 2 +
|
|
10565
|
+
const centerY = pos.y + scaledFloorHeight / 2 + scaledDiamondTopY;
|
|
10516
10566
|
ctx.fillStyle = "rgba(0, 0, 0, 0.7)";
|
|
10517
10567
|
ctx.font = `${12 * scale * 2}px monospace`;
|
|
10518
10568
|
ctx.textAlign = "center";
|
|
@@ -10532,7 +10582,7 @@ function IsometricCanvas({
|
|
|
10532
10582
|
const spriteUrl = feature.sprite || resolveFeatureSpriteUrl(feature.type);
|
|
10533
10583
|
const img = spriteUrl ? getImage(spriteUrl) : null;
|
|
10534
10584
|
const centerX = pos.x + scaledTileWidth / 2;
|
|
10535
|
-
const featureGroundY = pos.y +
|
|
10585
|
+
const featureGroundY = pos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
|
|
10536
10586
|
const isCastle = feature.type === "castle";
|
|
10537
10587
|
const featureDrawH = isCastle ? scaledFloorHeight * 3.5 : scaledFloorHeight * 1.6;
|
|
10538
10588
|
const maxFeatureW = isCastle ? scaledTileWidth * 1.8 : scaledTileWidth * 0.7;
|
|
@@ -10558,7 +10608,8 @@ function IsometricCanvas({
|
|
|
10558
10608
|
ctx.stroke();
|
|
10559
10609
|
}
|
|
10560
10610
|
}
|
|
10561
|
-
const
|
|
10611
|
+
const unitsWithPosition = units.filter((u) => !!u.position);
|
|
10612
|
+
const sortedUnits = [...unitsWithPosition].sort((a, b) => {
|
|
10562
10613
|
const depthA = a.position.x + a.position.y;
|
|
10563
10614
|
const depthB = b.position.x + b.position.y;
|
|
10564
10615
|
return depthA !== depthB ? depthA - depthB : a.position.y - b.position.y;
|
|
@@ -10570,7 +10621,7 @@ function IsometricCanvas({
|
|
|
10570
10621
|
}
|
|
10571
10622
|
const isSelected = unit.id === selectedUnitId;
|
|
10572
10623
|
const centerX = pos.x + scaledTileWidth / 2;
|
|
10573
|
-
const groundY = pos.y +
|
|
10624
|
+
const groundY = pos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
|
|
10574
10625
|
const breatheOffset = 0.8 * scale * (1 + Math.sin(animTime * 2e-3 + (unit.position.x * 3.7 + unit.position.y * 5.3)));
|
|
10575
10626
|
const unitSpriteUrl = resolveUnitSpriteUrl(unit);
|
|
10576
10627
|
const img = unitSpriteUrl ? getImage(unitSpriteUrl) : null;
|
|
@@ -10586,7 +10637,7 @@ function IsometricCanvas({
|
|
|
10586
10637
|
if (unit.previousPosition && (unit.previousPosition.x !== unit.position.x || unit.previousPosition.y !== unit.position.y)) {
|
|
10587
10638
|
const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX);
|
|
10588
10639
|
const ghostCenterX = ghostPos.x + scaledTileWidth / 2;
|
|
10589
|
-
const ghostGroundY = ghostPos.y +
|
|
10640
|
+
const ghostGroundY = ghostPos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
|
|
10590
10641
|
ctx.save();
|
|
10591
10642
|
ctx.globalAlpha = 0.25;
|
|
10592
10643
|
if (img) {
|
|
@@ -10726,6 +10777,7 @@ function IsometricCanvas({
|
|
|
10726
10777
|
scaledTileWidth,
|
|
10727
10778
|
scaledTileHeight,
|
|
10728
10779
|
scaledFloorHeight,
|
|
10780
|
+
scaledDiamondTopY,
|
|
10729
10781
|
validMoveSet,
|
|
10730
10782
|
attackTargetSet,
|
|
10731
10783
|
hoveredTile,
|
|
@@ -10739,15 +10791,15 @@ function IsometricCanvas({
|
|
|
10739
10791
|
useEffect(() => {
|
|
10740
10792
|
if (!selectedUnitId) return;
|
|
10741
10793
|
const unit = units.find((u) => u.id === selectedUnitId);
|
|
10742
|
-
if (!unit) return;
|
|
10794
|
+
if (!unit?.position) return;
|
|
10743
10795
|
const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
|
|
10744
10796
|
const centerX = pos.x + scaledTileWidth / 2;
|
|
10745
|
-
const centerY = pos.y +
|
|
10797
|
+
const centerY = pos.y + scaledDiamondTopY + scaledFloorHeight / 2;
|
|
10746
10798
|
targetCameraRef.current = {
|
|
10747
10799
|
x: centerX - viewportSize.width / 2,
|
|
10748
10800
|
y: centerY - viewportSize.height / 2
|
|
10749
10801
|
};
|
|
10750
|
-
}, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth,
|
|
10802
|
+
}, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
|
|
10751
10803
|
useEffect(() => {
|
|
10752
10804
|
const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2;
|
|
10753
10805
|
draw(animTimeRef.current);
|
|
@@ -10774,14 +10826,14 @@ function IsometricCanvas({
|
|
|
10774
10826
|
if (!onTileHover && !tileHoverEvent || !canvasRef.current) return;
|
|
10775
10827
|
const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
|
|
10776
10828
|
const adjustedX = world.x - scaledTileWidth / 2;
|
|
10777
|
-
const adjustedY = world.y -
|
|
10829
|
+
const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
|
|
10778
10830
|
const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
|
|
10779
10831
|
const tileExists = tilesProp.some((t) => t.x === isoPos.x && t.y === isoPos.y);
|
|
10780
10832
|
if (tileExists) {
|
|
10781
10833
|
if (tileHoverEvent) eventBus.emit(`UI:${tileHoverEvent}`, { x: isoPos.x, y: isoPos.y });
|
|
10782
10834
|
onTileHover?.(isoPos.x, isoPos.y);
|
|
10783
10835
|
}
|
|
10784
|
-
}, [enableCamera, handleMouseMove, draw, onTileHover, screenToWorld, viewportSize, scaledTileWidth,
|
|
10836
|
+
}, [enableCamera, handleMouseMove, draw, onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
|
|
10785
10837
|
const handleMouseLeaveWithCamera = useCallback(() => {
|
|
10786
10838
|
handleMouseLeave();
|
|
10787
10839
|
if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
|
|
@@ -10797,9 +10849,9 @@ function IsometricCanvas({
|
|
|
10797
10849
|
if (!canvasRef.current) return;
|
|
10798
10850
|
const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
|
|
10799
10851
|
const adjustedX = world.x - scaledTileWidth / 2;
|
|
10800
|
-
const adjustedY = world.y -
|
|
10852
|
+
const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
|
|
10801
10853
|
const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
|
|
10802
|
-
const clickedUnit = units.find((u) => u.position
|
|
10854
|
+
const clickedUnit = units.find((u) => u.position?.x === isoPos.x && u.position?.y === isoPos.y);
|
|
10803
10855
|
if (clickedUnit && (onUnitClick || unitClickEvent)) {
|
|
10804
10856
|
if (unitClickEvent) eventBus.emit(`UI:${unitClickEvent}`, { unitId: clickedUnit.id });
|
|
10805
10857
|
onUnitClick?.(clickedUnit.id);
|
|
@@ -10810,7 +10862,7 @@ function IsometricCanvas({
|
|
|
10810
10862
|
onTileClick?.(isoPos.x, isoPos.y);
|
|
10811
10863
|
}
|
|
10812
10864
|
}
|
|
10813
|
-
}, [dragDistance, screenToWorld, viewportSize, scaledTileWidth,
|
|
10865
|
+
}, [dragDistance, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, units, tilesProp, onUnitClick, onTileClick, unitClickEvent, tileClickEvent, eventBus]);
|
|
10814
10866
|
if (error) {
|
|
10815
10867
|
return /* @__PURE__ */ jsx(ErrorState, { title: "Canvas Error", message: error.message, className });
|
|
10816
10868
|
}
|
|
@@ -12229,7 +12281,7 @@ function useSpriteAnimations(getSheetUrls, getFrameDims, options = {}) {
|
|
|
12229
12281
|
const sheetUrls = getSheetUrls(unit);
|
|
12230
12282
|
if (!sheetUrls) continue;
|
|
12231
12283
|
const prev = prevPos.get(unit.id);
|
|
12232
|
-
if (prev) {
|
|
12284
|
+
if (prev && unit.position) {
|
|
12233
12285
|
const dx = unit.position.x - prev.x;
|
|
12234
12286
|
const dy = unit.position.y - prev.y;
|
|
12235
12287
|
if (dx !== 0 || dy !== 0) {
|
|
@@ -12248,7 +12300,9 @@ function useSpriteAnimations(getSheetUrls, getFrameDims, options = {}) {
|
|
|
12248
12300
|
}
|
|
12249
12301
|
}
|
|
12250
12302
|
}
|
|
12251
|
-
|
|
12303
|
+
if (unit.position) {
|
|
12304
|
+
prevPos.set(unit.id, { x: unit.position.x, y: unit.position.y });
|
|
12305
|
+
}
|
|
12252
12306
|
state = tickAnimationState(state, scaledDelta);
|
|
12253
12307
|
states.set(unit.id, state);
|
|
12254
12308
|
}
|
|
@@ -12287,6 +12341,283 @@ function useSpriteAnimations(getSheetUrls, getFrameDims, options = {}) {
|
|
|
12287
12341
|
}, [getSheetUrls, getFrameDims]);
|
|
12288
12342
|
return { syncUnits, setUnitAnimation, resolveUnitFrame };
|
|
12289
12343
|
}
|
|
12344
|
+
|
|
12345
|
+
// components/organisms/game/managers/PhysicsManager.ts
|
|
12346
|
+
var PhysicsManager = class {
|
|
12347
|
+
constructor(config = {}) {
|
|
12348
|
+
__publicField(this, "entities", /* @__PURE__ */ new Map());
|
|
12349
|
+
__publicField(this, "config");
|
|
12350
|
+
this.config = {
|
|
12351
|
+
gravity: 0.5,
|
|
12352
|
+
friction: 0.8,
|
|
12353
|
+
airResistance: 0.99,
|
|
12354
|
+
maxVelocityY: 20,
|
|
12355
|
+
groundY: 500,
|
|
12356
|
+
// Default ground position in pixels
|
|
12357
|
+
...config
|
|
12358
|
+
};
|
|
12359
|
+
}
|
|
12360
|
+
/**
|
|
12361
|
+
* Register an entity for physics simulation
|
|
12362
|
+
*/
|
|
12363
|
+
registerEntity(entityId, initialState = {}) {
|
|
12364
|
+
const state = {
|
|
12365
|
+
id: entityId,
|
|
12366
|
+
x: initialState.x ?? 0,
|
|
12367
|
+
y: initialState.y ?? 0,
|
|
12368
|
+
vx: initialState.vx ?? 0,
|
|
12369
|
+
vy: initialState.vy ?? 0,
|
|
12370
|
+
isGrounded: initialState.isGrounded ?? false,
|
|
12371
|
+
gravity: initialState.gravity ?? this.config.gravity,
|
|
12372
|
+
friction: initialState.friction ?? this.config.friction,
|
|
12373
|
+
airResistance: initialState.airResistance ?? this.config.airResistance,
|
|
12374
|
+
maxVelocityY: initialState.maxVelocityY ?? this.config.maxVelocityY,
|
|
12375
|
+
mass: initialState.mass ?? 1,
|
|
12376
|
+
restitution: initialState.restitution ?? 0.8,
|
|
12377
|
+
state: initialState.state ?? "Active"
|
|
12378
|
+
};
|
|
12379
|
+
this.entities.set(entityId, state);
|
|
12380
|
+
return state;
|
|
12381
|
+
}
|
|
12382
|
+
/**
|
|
12383
|
+
* Unregister an entity from physics simulation
|
|
12384
|
+
*/
|
|
12385
|
+
unregisterEntity(entityId) {
|
|
12386
|
+
this.entities.delete(entityId);
|
|
12387
|
+
}
|
|
12388
|
+
/**
|
|
12389
|
+
* Get physics state for an entity
|
|
12390
|
+
*/
|
|
12391
|
+
getState(entityId) {
|
|
12392
|
+
return this.entities.get(entityId);
|
|
12393
|
+
}
|
|
12394
|
+
/**
|
|
12395
|
+
* Get all registered entities
|
|
12396
|
+
*/
|
|
12397
|
+
getAllEntities() {
|
|
12398
|
+
return Array.from(this.entities.values());
|
|
12399
|
+
}
|
|
12400
|
+
/**
|
|
12401
|
+
* Apply a force to an entity (impulse)
|
|
12402
|
+
*/
|
|
12403
|
+
applyForce(entityId, fx, fy) {
|
|
12404
|
+
const state = this.entities.get(entityId);
|
|
12405
|
+
if (!state || state.state !== "Active") return;
|
|
12406
|
+
state.vx += fx;
|
|
12407
|
+
state.vy += fy;
|
|
12408
|
+
}
|
|
12409
|
+
/**
|
|
12410
|
+
* Set velocity directly
|
|
12411
|
+
*/
|
|
12412
|
+
setVelocity(entityId, vx, vy) {
|
|
12413
|
+
const state = this.entities.get(entityId);
|
|
12414
|
+
if (!state) return;
|
|
12415
|
+
state.vx = vx;
|
|
12416
|
+
state.vy = vy;
|
|
12417
|
+
}
|
|
12418
|
+
/**
|
|
12419
|
+
* Set position directly
|
|
12420
|
+
*/
|
|
12421
|
+
setPosition(entityId, x, y) {
|
|
12422
|
+
const state = this.entities.get(entityId);
|
|
12423
|
+
if (!state) return;
|
|
12424
|
+
state.x = x;
|
|
12425
|
+
state.y = y;
|
|
12426
|
+
}
|
|
12427
|
+
/**
|
|
12428
|
+
* Freeze/unfreeze an entity
|
|
12429
|
+
*/
|
|
12430
|
+
setFrozen(entityId, frozen) {
|
|
12431
|
+
const state = this.entities.get(entityId);
|
|
12432
|
+
if (!state) return;
|
|
12433
|
+
state.state = frozen ? "Frozen" : "Active";
|
|
12434
|
+
}
|
|
12435
|
+
/**
|
|
12436
|
+
* Main tick function - call this every frame
|
|
12437
|
+
* Implements the logic from std-physics2d ticks
|
|
12438
|
+
*/
|
|
12439
|
+
tick(deltaTime = 16) {
|
|
12440
|
+
for (const state of this.entities.values()) {
|
|
12441
|
+
if (state.state !== "Active") continue;
|
|
12442
|
+
this.applyGravity(state, deltaTime);
|
|
12443
|
+
this.applyVelocity(state, deltaTime);
|
|
12444
|
+
this.checkGroundCollision(state);
|
|
12445
|
+
}
|
|
12446
|
+
}
|
|
12447
|
+
/**
|
|
12448
|
+
* ApplyGravity tick implementation
|
|
12449
|
+
*/
|
|
12450
|
+
applyGravity(state, deltaTime) {
|
|
12451
|
+
if (state.isGrounded) return;
|
|
12452
|
+
const gravityForce = state.gravity * (deltaTime / 16);
|
|
12453
|
+
state.vy = Math.min(state.maxVelocityY, state.vy + gravityForce);
|
|
12454
|
+
}
|
|
12455
|
+
/**
|
|
12456
|
+
* ApplyVelocity tick implementation
|
|
12457
|
+
*/
|
|
12458
|
+
applyVelocity(state, deltaTime) {
|
|
12459
|
+
const dt = deltaTime / 16;
|
|
12460
|
+
state.vx *= Math.pow(state.airResistance, dt);
|
|
12461
|
+
state.x += state.vx * dt;
|
|
12462
|
+
state.y += state.vy * dt;
|
|
12463
|
+
}
|
|
12464
|
+
/**
|
|
12465
|
+
* Check and handle ground collision
|
|
12466
|
+
*/
|
|
12467
|
+
checkGroundCollision(state) {
|
|
12468
|
+
const groundY = this.config.groundY;
|
|
12469
|
+
if (state.y >= groundY) {
|
|
12470
|
+
state.y = groundY;
|
|
12471
|
+
state.isGrounded = true;
|
|
12472
|
+
state.vy = 0;
|
|
12473
|
+
state.vx *= state.friction;
|
|
12474
|
+
if (Math.abs(state.vx) < 0.01) {
|
|
12475
|
+
state.vx = 0;
|
|
12476
|
+
}
|
|
12477
|
+
} else {
|
|
12478
|
+
state.isGrounded = false;
|
|
12479
|
+
}
|
|
12480
|
+
}
|
|
12481
|
+
/**
|
|
12482
|
+
* Check AABB collision between two entities
|
|
12483
|
+
*/
|
|
12484
|
+
checkCollision(entityIdA, entityIdB, boundsA, boundsB) {
|
|
12485
|
+
const stateA = this.entities.get(entityIdA);
|
|
12486
|
+
const stateB = this.entities.get(entityIdB);
|
|
12487
|
+
if (!stateA || !stateB) return false;
|
|
12488
|
+
const absBoundsA = {
|
|
12489
|
+
x: stateA.x + boundsA.x,
|
|
12490
|
+
y: stateA.y + boundsA.y,
|
|
12491
|
+
width: boundsA.width,
|
|
12492
|
+
height: boundsA.height
|
|
12493
|
+
};
|
|
12494
|
+
const absBoundsB = {
|
|
12495
|
+
x: stateB.x + boundsB.x,
|
|
12496
|
+
y: stateB.y + boundsB.y,
|
|
12497
|
+
width: boundsB.width,
|
|
12498
|
+
height: boundsB.height
|
|
12499
|
+
};
|
|
12500
|
+
return absBoundsA.x < absBoundsB.x + absBoundsB.width && absBoundsA.x + absBoundsA.width > absBoundsB.x && absBoundsA.y < absBoundsB.y + absBoundsB.height && absBoundsA.y + absBoundsA.height > absBoundsB.y;
|
|
12501
|
+
}
|
|
12502
|
+
/**
|
|
12503
|
+
* Resolve collision with bounce
|
|
12504
|
+
*/
|
|
12505
|
+
resolveCollision(entityIdA, entityIdB) {
|
|
12506
|
+
const stateA = this.entities.get(entityIdA);
|
|
12507
|
+
const stateB = this.entities.get(entityIdB);
|
|
12508
|
+
if (!stateA || !stateB) return;
|
|
12509
|
+
const restitution = Math.min(stateA.restitution ?? 0.8, stateB.restitution ?? 0.8);
|
|
12510
|
+
const tempVx = stateA.vx;
|
|
12511
|
+
const tempVy = stateA.vy;
|
|
12512
|
+
stateA.vx = stateB.vx * restitution;
|
|
12513
|
+
stateA.vy = stateB.vy * restitution;
|
|
12514
|
+
stateB.vx = tempVx * restitution;
|
|
12515
|
+
stateB.vy = tempVy * restitution;
|
|
12516
|
+
const dx = stateB.x - stateA.x;
|
|
12517
|
+
const dy = stateB.y - stateA.y;
|
|
12518
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
12519
|
+
if (distance > 0) {
|
|
12520
|
+
const overlap = 1;
|
|
12521
|
+
const nx = dx / distance;
|
|
12522
|
+
const ny = dy / distance;
|
|
12523
|
+
stateA.x -= nx * overlap * 0.5;
|
|
12524
|
+
stateA.y -= ny * overlap * 0.5;
|
|
12525
|
+
stateB.x += nx * overlap * 0.5;
|
|
12526
|
+
stateB.y += ny * overlap * 0.5;
|
|
12527
|
+
}
|
|
12528
|
+
}
|
|
12529
|
+
/**
|
|
12530
|
+
* Reset all physics state
|
|
12531
|
+
*/
|
|
12532
|
+
reset() {
|
|
12533
|
+
this.entities.clear();
|
|
12534
|
+
}
|
|
12535
|
+
};
|
|
12536
|
+
|
|
12537
|
+
// components/organisms/game/hooks/usePhysics2D.ts
|
|
12538
|
+
function usePhysics2D(options = {}) {
|
|
12539
|
+
const physicsManagerRef = useRef(null);
|
|
12540
|
+
const collisionCallbacksRef = useRef(/* @__PURE__ */ new Set());
|
|
12541
|
+
if (!physicsManagerRef.current) {
|
|
12542
|
+
physicsManagerRef.current = new PhysicsManager({
|
|
12543
|
+
gravity: options.gravity,
|
|
12544
|
+
friction: options.friction,
|
|
12545
|
+
airResistance: options.airResistance,
|
|
12546
|
+
maxVelocityY: options.maxVelocityY,
|
|
12547
|
+
groundY: options.groundY
|
|
12548
|
+
});
|
|
12549
|
+
}
|
|
12550
|
+
const manager = physicsManagerRef.current;
|
|
12551
|
+
useEffect(() => {
|
|
12552
|
+
if (options.onCollision) {
|
|
12553
|
+
collisionCallbacksRef.current.add(options.onCollision);
|
|
12554
|
+
}
|
|
12555
|
+
return () => {
|
|
12556
|
+
if (options.onCollision) {
|
|
12557
|
+
collisionCallbacksRef.current.delete(options.onCollision);
|
|
12558
|
+
}
|
|
12559
|
+
};
|
|
12560
|
+
}, [options.onCollision]);
|
|
12561
|
+
const registerUnit = useCallback((unitId, initialState = {}) => {
|
|
12562
|
+
manager.registerEntity(unitId, initialState);
|
|
12563
|
+
}, [manager]);
|
|
12564
|
+
const unregisterUnit = useCallback((unitId) => {
|
|
12565
|
+
manager.unregisterEntity(unitId);
|
|
12566
|
+
}, [manager]);
|
|
12567
|
+
const getPosition = useCallback((unitId) => {
|
|
12568
|
+
const state = manager.getState(unitId);
|
|
12569
|
+
if (!state) return null;
|
|
12570
|
+
return { x: state.x, y: state.y };
|
|
12571
|
+
}, [manager]);
|
|
12572
|
+
const getState = useCallback((unitId) => {
|
|
12573
|
+
return manager.getState(unitId);
|
|
12574
|
+
}, [manager]);
|
|
12575
|
+
const applyForce = useCallback((unitId, fx, fy) => {
|
|
12576
|
+
manager.applyForce(unitId, fx, fy);
|
|
12577
|
+
}, [manager]);
|
|
12578
|
+
const setVelocity = useCallback((unitId, vx, vy) => {
|
|
12579
|
+
manager.setVelocity(unitId, vx, vy);
|
|
12580
|
+
}, [manager]);
|
|
12581
|
+
const setPosition = useCallback((unitId, x, y) => {
|
|
12582
|
+
manager.setPosition(unitId, x, y);
|
|
12583
|
+
}, [manager]);
|
|
12584
|
+
const tick = useCallback((deltaTime = 16) => {
|
|
12585
|
+
manager.tick(deltaTime);
|
|
12586
|
+
}, [manager]);
|
|
12587
|
+
const checkCollision = useCallback((unitIdA, unitIdB, boundsA, boundsB) => {
|
|
12588
|
+
return manager.checkCollision(unitIdA, unitIdB, boundsA, boundsB);
|
|
12589
|
+
}, [manager]);
|
|
12590
|
+
const resolveCollision = useCallback((unitIdA, unitIdB) => {
|
|
12591
|
+
manager.resolveCollision(unitIdA, unitIdB);
|
|
12592
|
+
collisionCallbacksRef.current.forEach((callback) => {
|
|
12593
|
+
callback(unitIdA, unitIdB);
|
|
12594
|
+
});
|
|
12595
|
+
}, [manager]);
|
|
12596
|
+
const setFrozen = useCallback((unitId, frozen) => {
|
|
12597
|
+
manager.setFrozen(unitId, frozen);
|
|
12598
|
+
}, [manager]);
|
|
12599
|
+
const getAllUnits = useCallback(() => {
|
|
12600
|
+
return manager.getAllEntities();
|
|
12601
|
+
}, [manager]);
|
|
12602
|
+
const reset = useCallback(() => {
|
|
12603
|
+
manager.reset();
|
|
12604
|
+
}, [manager]);
|
|
12605
|
+
return {
|
|
12606
|
+
registerUnit,
|
|
12607
|
+
unregisterUnit,
|
|
12608
|
+
getPosition,
|
|
12609
|
+
getState,
|
|
12610
|
+
applyForce,
|
|
12611
|
+
setVelocity,
|
|
12612
|
+
setPosition,
|
|
12613
|
+
tick,
|
|
12614
|
+
checkCollision,
|
|
12615
|
+
resolveCollision,
|
|
12616
|
+
setFrozen,
|
|
12617
|
+
getAllUnits,
|
|
12618
|
+
reset
|
|
12619
|
+
};
|
|
12620
|
+
}
|
|
12290
12621
|
var sizeMap4 = {
|
|
12291
12622
|
sm: "text-xs px-2 py-1",
|
|
12292
12623
|
md: "text-sm px-3 py-1.5",
|
|
@@ -12981,172 +13312,1032 @@ function DialogueBox({
|
|
|
12981
13312
|
}
|
|
12982
13313
|
);
|
|
12983
13314
|
}
|
|
12984
|
-
function
|
|
12985
|
-
|
|
12986
|
-
|
|
12987
|
-
|
|
12988
|
-
|
|
12989
|
-
|
|
12990
|
-
|
|
12991
|
-
|
|
12992
|
-
|
|
12993
|
-
|
|
12994
|
-
|
|
12995
|
-
|
|
12996
|
-
|
|
12997
|
-
|
|
12998
|
-
|
|
12999
|
-
|
|
13000
|
-
|
|
13001
|
-
|
|
13002
|
-
|
|
13003
|
-
|
|
13004
|
-
|
|
13005
|
-
|
|
13006
|
-
|
|
13007
|
-
|
|
13008
|
-
p: p2,
|
|
13009
|
-
m,
|
|
13010
|
-
bg = "transparent",
|
|
13011
|
-
border = false,
|
|
13012
|
-
radius = "none",
|
|
13013
|
-
shadow = "none",
|
|
13014
|
-
className,
|
|
13015
|
-
style,
|
|
13016
|
-
children
|
|
13017
|
-
}) {
|
|
13018
|
-
return /* @__PURE__ */ jsx(
|
|
13019
|
-
Box,
|
|
13020
|
-
{
|
|
13021
|
-
padding: p2,
|
|
13022
|
-
margin: m,
|
|
13023
|
-
bg,
|
|
13024
|
-
border,
|
|
13025
|
-
rounded: radius,
|
|
13026
|
-
shadow,
|
|
13027
|
-
className,
|
|
13028
|
-
style,
|
|
13029
|
-
children
|
|
13030
|
-
}
|
|
13031
|
-
);
|
|
13032
|
-
}
|
|
13033
|
-
BoxPattern.displayName = "BoxPattern";
|
|
13034
|
-
function GridPattern({
|
|
13035
|
-
cols = 1,
|
|
13036
|
-
gap = "md",
|
|
13037
|
-
rowGap,
|
|
13038
|
-
colGap,
|
|
13039
|
-
className,
|
|
13040
|
-
style,
|
|
13041
|
-
children
|
|
13042
|
-
}) {
|
|
13043
|
-
return /* @__PURE__ */ jsx(Grid2, { cols, gap, rowGap, colGap, className, style, children });
|
|
13044
|
-
}
|
|
13045
|
-
GridPattern.displayName = "GridPattern";
|
|
13046
|
-
function CenterPattern({
|
|
13047
|
-
minHeight,
|
|
13048
|
-
className,
|
|
13049
|
-
style,
|
|
13050
|
-
children
|
|
13051
|
-
}) {
|
|
13052
|
-
const mergedStyle = minHeight ? { minHeight, ...style } : style;
|
|
13053
|
-
return /* @__PURE__ */ jsx(Center, { className, style: mergedStyle, children });
|
|
13054
|
-
}
|
|
13055
|
-
CenterPattern.displayName = "CenterPattern";
|
|
13056
|
-
function SpacerPattern({ size = "flex" }) {
|
|
13057
|
-
if (size === "flex") {
|
|
13058
|
-
return /* @__PURE__ */ jsx(Spacer, {});
|
|
13059
|
-
}
|
|
13060
|
-
const sizeMap5 = {
|
|
13061
|
-
xs: "0.25rem",
|
|
13062
|
-
sm: "0.5rem",
|
|
13063
|
-
md: "1rem",
|
|
13064
|
-
lg: "1.5rem",
|
|
13065
|
-
xl: "2rem"
|
|
13066
|
-
};
|
|
13067
|
-
return /* @__PURE__ */ jsx("div", { style: { width: sizeMap5[size], height: sizeMap5[size], flexShrink: 0 } });
|
|
13068
|
-
}
|
|
13069
|
-
SpacerPattern.displayName = "SpacerPattern";
|
|
13070
|
-
function DividerPattern({
|
|
13071
|
-
orientation = "horizontal",
|
|
13072
|
-
variant = "solid",
|
|
13073
|
-
spacing = "md"
|
|
13074
|
-
}) {
|
|
13075
|
-
const spacingMap = {
|
|
13076
|
-
xs: "my-1",
|
|
13077
|
-
sm: "my-2",
|
|
13078
|
-
md: "my-4",
|
|
13079
|
-
lg: "my-6"
|
|
13080
|
-
};
|
|
13081
|
-
const verticalSpacingMap = {
|
|
13082
|
-
xs: "mx-1",
|
|
13083
|
-
sm: "mx-2",
|
|
13084
|
-
md: "mx-4",
|
|
13085
|
-
lg: "mx-6"
|
|
13086
|
-
};
|
|
13087
|
-
return /* @__PURE__ */ jsx(
|
|
13088
|
-
Divider,
|
|
13089
|
-
{
|
|
13090
|
-
orientation,
|
|
13091
|
-
variant,
|
|
13092
|
-
className: orientation === "horizontal" ? spacingMap[spacing] : verticalSpacingMap[spacing]
|
|
13093
|
-
}
|
|
13094
|
-
);
|
|
13095
|
-
}
|
|
13096
|
-
DividerPattern.displayName = "DividerPattern";
|
|
13097
|
-
function ButtonPattern({
|
|
13098
|
-
label,
|
|
13099
|
-
variant = "primary",
|
|
13100
|
-
size = "md",
|
|
13101
|
-
disabled = false,
|
|
13102
|
-
onClick,
|
|
13103
|
-
icon,
|
|
13104
|
-
iconPosition = "left",
|
|
13315
|
+
function BattleBoard({
|
|
13316
|
+
entity,
|
|
13317
|
+
scale = 0.45,
|
|
13318
|
+
unitScale = 1,
|
|
13319
|
+
header,
|
|
13320
|
+
sidebar,
|
|
13321
|
+
actions,
|
|
13322
|
+
overlay,
|
|
13323
|
+
gameOverOverlay,
|
|
13324
|
+
onAttack,
|
|
13325
|
+
onGameEnd,
|
|
13326
|
+
onUnitMove,
|
|
13327
|
+
calculateDamage,
|
|
13328
|
+
onDrawEffects,
|
|
13329
|
+
hasActiveEffects: hasActiveEffects2 = false,
|
|
13330
|
+
effectSpriteUrls = [],
|
|
13331
|
+
resolveUnitFrame,
|
|
13332
|
+
tileClickEvent,
|
|
13333
|
+
unitClickEvent,
|
|
13334
|
+
endTurnEvent,
|
|
13335
|
+
cancelEvent,
|
|
13336
|
+
gameEndEvent,
|
|
13337
|
+
playAgainEvent,
|
|
13338
|
+
attackEvent,
|
|
13105
13339
|
className
|
|
13106
13340
|
}) {
|
|
13107
|
-
const
|
|
13108
|
-
const
|
|
13109
|
-
|
|
13110
|
-
|
|
13111
|
-
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
|
|
13115
|
-
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
13121
|
-
|
|
13122
|
-
|
|
13123
|
-
|
|
13124
|
-
|
|
13125
|
-
]
|
|
13126
|
-
}
|
|
13341
|
+
const initialUnits = entity.initialUnits;
|
|
13342
|
+
const tiles = entity.tiles;
|
|
13343
|
+
const features = entity.features ?? [];
|
|
13344
|
+
const boardWidth = entity.boardWidth ?? 8;
|
|
13345
|
+
const boardHeight = entity.boardHeight ?? 6;
|
|
13346
|
+
const assetManifest = entity.assetManifest;
|
|
13347
|
+
const backgroundImage = entity.backgroundImage;
|
|
13348
|
+
const eventBus = useEventBus();
|
|
13349
|
+
const [units, setUnits] = useState(initialUnits);
|
|
13350
|
+
const [selectedUnitId, setSelectedUnitId] = useState(null);
|
|
13351
|
+
const [hoveredTile, setHoveredTile] = useState(null);
|
|
13352
|
+
const [currentPhase, setCurrentPhase] = useState("observation");
|
|
13353
|
+
const [currentTurn, setCurrentTurn] = useState(1);
|
|
13354
|
+
const [gameResult, setGameResult] = useState(null);
|
|
13355
|
+
const [isShaking, setIsShaking] = useState(false);
|
|
13356
|
+
const selectedUnit = useMemo(
|
|
13357
|
+
() => units.find((u) => u.id === selectedUnitId) ?? null,
|
|
13358
|
+
[units, selectedUnitId]
|
|
13127
13359
|
);
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
|
|
13131
|
-
|
|
13132
|
-
|
|
13133
|
-
|
|
13134
|
-
|
|
13135
|
-
|
|
13136
|
-
|
|
13137
|
-
|
|
13138
|
-
|
|
13139
|
-
|
|
13140
|
-
|
|
13141
|
-
|
|
13142
|
-
|
|
13143
|
-
|
|
13144
|
-
|
|
13145
|
-
|
|
13146
|
-
|
|
13147
|
-
|
|
13148
|
-
|
|
13149
|
-
|
|
13360
|
+
const hoveredUnit = useMemo(() => {
|
|
13361
|
+
if (!hoveredTile) return null;
|
|
13362
|
+
return units.find(
|
|
13363
|
+
(u) => u.position.x === hoveredTile.x && u.position.y === hoveredTile.y && u.health > 0
|
|
13364
|
+
) ?? null;
|
|
13365
|
+
}, [hoveredTile, units]);
|
|
13366
|
+
const playerUnits = useMemo(() => units.filter((u) => u.team === "player" && u.health > 0), [units]);
|
|
13367
|
+
const enemyUnits = useMemo(() => units.filter((u) => u.team === "enemy" && u.health > 0), [units]);
|
|
13368
|
+
const validMoves = useMemo(() => {
|
|
13369
|
+
if (!selectedUnit || currentPhase !== "movement") return [];
|
|
13370
|
+
const moves = [];
|
|
13371
|
+
const range = selectedUnit.movement;
|
|
13372
|
+
for (let dy = -range; dy <= range; dy++) {
|
|
13373
|
+
for (let dx = -range; dx <= range; dx++) {
|
|
13374
|
+
const nx = selectedUnit.position.x + dx;
|
|
13375
|
+
const ny = selectedUnit.position.y + dy;
|
|
13376
|
+
const dist = Math.abs(dx) + Math.abs(dy);
|
|
13377
|
+
if (dist > 0 && dist <= range && nx >= 0 && nx < boardWidth && ny >= 0 && ny < boardHeight && !units.some((u) => u.position.x === nx && u.position.y === ny && u.health > 0)) {
|
|
13378
|
+
moves.push({ x: nx, y: ny });
|
|
13379
|
+
}
|
|
13380
|
+
}
|
|
13381
|
+
}
|
|
13382
|
+
return moves;
|
|
13383
|
+
}, [selectedUnit, currentPhase, units, boardWidth, boardHeight]);
|
|
13384
|
+
const attackTargets = useMemo(() => {
|
|
13385
|
+
if (!selectedUnit || currentPhase !== "action") return [];
|
|
13386
|
+
return units.filter((u) => u.team !== selectedUnit.team && u.health > 0).filter((u) => {
|
|
13387
|
+
const dx = Math.abs(u.position.x - selectedUnit.position.x);
|
|
13388
|
+
const dy = Math.abs(u.position.y - selectedUnit.position.y);
|
|
13389
|
+
return dx <= 1 && dy <= 1 && dx + dy > 0;
|
|
13390
|
+
}).map((u) => u.position);
|
|
13391
|
+
}, [selectedUnit, currentPhase, units]);
|
|
13392
|
+
const MOVE_SPEED_MS_PER_TILE = 300;
|
|
13393
|
+
const movementAnimRef = useRef(null);
|
|
13394
|
+
const [movingPositions, setMovingPositions] = useState(/* @__PURE__ */ new Map());
|
|
13395
|
+
const startMoveAnimation = useCallback((unitId, from, to, onComplete) => {
|
|
13396
|
+
const dx = to.x - from.x;
|
|
13397
|
+
const dy = to.y - from.y;
|
|
13398
|
+
const dist = Math.max(Math.abs(dx), Math.abs(dy));
|
|
13399
|
+
const duration = dist * MOVE_SPEED_MS_PER_TILE;
|
|
13400
|
+
movementAnimRef.current = { unitId, from, to, elapsed: 0, duration, onComplete };
|
|
13401
|
+
}, []);
|
|
13402
|
+
useEffect(() => {
|
|
13403
|
+
const interval = setInterval(() => {
|
|
13404
|
+
const anim2 = movementAnimRef.current;
|
|
13405
|
+
if (!anim2) return;
|
|
13406
|
+
anim2.elapsed += 16;
|
|
13407
|
+
const t = Math.min(anim2.elapsed / anim2.duration, 1);
|
|
13408
|
+
const eased = 1 - (1 - t) * (1 - t);
|
|
13409
|
+
const cx = anim2.from.x + (anim2.to.x - anim2.from.x) * eased;
|
|
13410
|
+
const cy = anim2.from.y + (anim2.to.y - anim2.from.y) * eased;
|
|
13411
|
+
if (t >= 1) {
|
|
13412
|
+
movementAnimRef.current = null;
|
|
13413
|
+
setMovingPositions((prev) => {
|
|
13414
|
+
const next = new Map(prev);
|
|
13415
|
+
next.delete(anim2.unitId);
|
|
13416
|
+
return next;
|
|
13417
|
+
});
|
|
13418
|
+
anim2.onComplete();
|
|
13419
|
+
} else {
|
|
13420
|
+
setMovingPositions((prev) => {
|
|
13421
|
+
const next = new Map(prev);
|
|
13422
|
+
next.set(anim2.unitId, { x: cx, y: cy });
|
|
13423
|
+
return next;
|
|
13424
|
+
});
|
|
13425
|
+
}
|
|
13426
|
+
}, 16);
|
|
13427
|
+
return () => clearInterval(interval);
|
|
13428
|
+
}, []);
|
|
13429
|
+
const isoUnits = useMemo(() => {
|
|
13430
|
+
return units.filter((u) => u.health > 0).map((unit) => {
|
|
13431
|
+
const pos = movingPositions.get(unit.id) ?? unit.position;
|
|
13432
|
+
return {
|
|
13433
|
+
id: unit.id,
|
|
13434
|
+
position: pos,
|
|
13435
|
+
name: unit.name,
|
|
13436
|
+
team: unit.team,
|
|
13437
|
+
health: unit.health,
|
|
13438
|
+
maxHealth: unit.maxHealth,
|
|
13439
|
+
unitType: unit.unitType,
|
|
13440
|
+
heroId: unit.heroId,
|
|
13441
|
+
sprite: unit.sprite,
|
|
13442
|
+
traits: unit.traits?.map((t) => ({
|
|
13443
|
+
name: t.name,
|
|
13444
|
+
currentState: t.currentState,
|
|
13445
|
+
states: t.states,
|
|
13446
|
+
cooldown: t.cooldown ?? 0
|
|
13447
|
+
}))
|
|
13448
|
+
};
|
|
13449
|
+
});
|
|
13450
|
+
}, [units, movingPositions]);
|
|
13451
|
+
const maxY = Math.max(...tiles.map((t) => t.y), 0);
|
|
13452
|
+
const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
|
|
13453
|
+
const tileToScreen = useCallback(
|
|
13454
|
+
(tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
|
|
13455
|
+
[scale, baseOffsetX]
|
|
13456
|
+
);
|
|
13457
|
+
const checkGameEnd = useCallback(() => {
|
|
13458
|
+
const pa = units.filter((u) => u.team === "player" && u.health > 0);
|
|
13459
|
+
const ea = units.filter((u) => u.team === "enemy" && u.health > 0);
|
|
13460
|
+
if (pa.length === 0) {
|
|
13461
|
+
setGameResult("defeat");
|
|
13462
|
+
setCurrentPhase("game_over");
|
|
13463
|
+
onGameEnd?.("defeat");
|
|
13464
|
+
if (gameEndEvent) {
|
|
13465
|
+
eventBus.emit(`UI:${gameEndEvent}`, { result: "defeat" });
|
|
13466
|
+
}
|
|
13467
|
+
} else if (ea.length === 0) {
|
|
13468
|
+
setGameResult("victory");
|
|
13469
|
+
setCurrentPhase("game_over");
|
|
13470
|
+
onGameEnd?.("victory");
|
|
13471
|
+
if (gameEndEvent) {
|
|
13472
|
+
eventBus.emit(`UI:${gameEndEvent}`, { result: "victory" });
|
|
13473
|
+
}
|
|
13474
|
+
}
|
|
13475
|
+
}, [units, onGameEnd, gameEndEvent, eventBus]);
|
|
13476
|
+
const handleUnitClick = useCallback((unitId) => {
|
|
13477
|
+
const unit = units.find((u) => u.id === unitId);
|
|
13478
|
+
if (!unit) return;
|
|
13479
|
+
if (unitClickEvent) {
|
|
13480
|
+
eventBus.emit(`UI:${unitClickEvent}`, { unitId });
|
|
13481
|
+
}
|
|
13482
|
+
if (currentPhase === "observation" || currentPhase === "selection") {
|
|
13483
|
+
if (unit.team === "player") {
|
|
13484
|
+
setSelectedUnitId(unitId);
|
|
13485
|
+
setCurrentPhase("movement");
|
|
13486
|
+
}
|
|
13487
|
+
} else if (currentPhase === "action" && selectedUnit) {
|
|
13488
|
+
if (unit.team === "enemy" && attackTargets.some((t) => t.x === unit.position.x && t.y === unit.position.y)) {
|
|
13489
|
+
const damage = calculateDamage ? calculateDamage(selectedUnit, unit) : Math.max(1, selectedUnit.attack - unit.defense);
|
|
13490
|
+
const newHealth = Math.max(0, unit.health - damage);
|
|
13491
|
+
setUnits((prev) => prev.map((u) => u.id === unit.id ? { ...u, health: newHealth } : u));
|
|
13492
|
+
setIsShaking(true);
|
|
13493
|
+
setTimeout(() => setIsShaking(false), 300);
|
|
13494
|
+
onAttack?.(selectedUnit, unit, damage);
|
|
13495
|
+
if (attackEvent) {
|
|
13496
|
+
eventBus.emit(`UI:${attackEvent}`, {
|
|
13497
|
+
attackerId: selectedUnit.id,
|
|
13498
|
+
targetId: unit.id,
|
|
13499
|
+
damage
|
|
13500
|
+
});
|
|
13501
|
+
}
|
|
13502
|
+
setSelectedUnitId(null);
|
|
13503
|
+
setCurrentPhase("observation");
|
|
13504
|
+
setCurrentTurn((t) => t + 1);
|
|
13505
|
+
setTimeout(checkGameEnd, 100);
|
|
13506
|
+
}
|
|
13507
|
+
}
|
|
13508
|
+
}, [currentPhase, selectedUnit, attackTargets, units, checkGameEnd, onAttack, calculateDamage, unitClickEvent, attackEvent, eventBus]);
|
|
13509
|
+
const handleTileClick = useCallback((x, y) => {
|
|
13510
|
+
if (tileClickEvent) {
|
|
13511
|
+
eventBus.emit(`UI:${tileClickEvent}`, { x, y });
|
|
13512
|
+
}
|
|
13513
|
+
if (currentPhase === "movement" && selectedUnit) {
|
|
13514
|
+
if (movementAnimRef.current) return;
|
|
13515
|
+
if (validMoves.some((m) => m.x === x && m.y === y)) {
|
|
13516
|
+
const from = { ...selectedUnit.position };
|
|
13517
|
+
const to = { x, y };
|
|
13518
|
+
startMoveAnimation(selectedUnit.id, from, to, () => {
|
|
13519
|
+
setUnits(
|
|
13520
|
+
(prev) => prev.map((u) => u.id === selectedUnitId ? { ...u, position: { x, y } } : u)
|
|
13521
|
+
);
|
|
13522
|
+
onUnitMove?.(selectedUnit, to);
|
|
13523
|
+
setCurrentPhase("action");
|
|
13524
|
+
});
|
|
13525
|
+
}
|
|
13526
|
+
}
|
|
13527
|
+
}, [currentPhase, selectedUnit, selectedUnitId, validMoves, startMoveAnimation, onUnitMove, tileClickEvent, eventBus]);
|
|
13528
|
+
const handleEndTurn = useCallback(() => {
|
|
13529
|
+
setSelectedUnitId(null);
|
|
13530
|
+
setCurrentPhase("observation");
|
|
13531
|
+
setCurrentTurn((t) => t + 1);
|
|
13532
|
+
if (endTurnEvent) {
|
|
13533
|
+
eventBus.emit(`UI:${endTurnEvent}`, {});
|
|
13534
|
+
}
|
|
13535
|
+
}, [endTurnEvent, eventBus]);
|
|
13536
|
+
const handleCancel = useCallback(() => {
|
|
13537
|
+
setSelectedUnitId(null);
|
|
13538
|
+
setCurrentPhase("observation");
|
|
13539
|
+
if (cancelEvent) {
|
|
13540
|
+
eventBus.emit(`UI:${cancelEvent}`, {});
|
|
13541
|
+
}
|
|
13542
|
+
}, [cancelEvent, eventBus]);
|
|
13543
|
+
const handleReset = useCallback(() => {
|
|
13544
|
+
setUnits(initialUnits);
|
|
13545
|
+
setSelectedUnitId(null);
|
|
13546
|
+
setCurrentPhase("observation");
|
|
13547
|
+
setCurrentTurn(1);
|
|
13548
|
+
setGameResult(null);
|
|
13549
|
+
if (playAgainEvent) {
|
|
13550
|
+
eventBus.emit(`UI:${playAgainEvent}`, {});
|
|
13551
|
+
}
|
|
13552
|
+
}, [initialUnits, playAgainEvent, eventBus]);
|
|
13553
|
+
const ctx = useMemo(
|
|
13554
|
+
() => ({
|
|
13555
|
+
phase: currentPhase,
|
|
13556
|
+
turn: currentTurn,
|
|
13557
|
+
selectedUnit,
|
|
13558
|
+
hoveredUnit,
|
|
13559
|
+
playerUnits,
|
|
13560
|
+
enemyUnits,
|
|
13561
|
+
gameResult,
|
|
13562
|
+
onEndTurn: handleEndTurn,
|
|
13563
|
+
onCancel: handleCancel,
|
|
13564
|
+
onReset: handleReset,
|
|
13565
|
+
attackTargets,
|
|
13566
|
+
tileToScreen
|
|
13567
|
+
}),
|
|
13568
|
+
[
|
|
13569
|
+
currentPhase,
|
|
13570
|
+
currentTurn,
|
|
13571
|
+
selectedUnit,
|
|
13572
|
+
hoveredUnit,
|
|
13573
|
+
playerUnits,
|
|
13574
|
+
enemyUnits,
|
|
13575
|
+
gameResult,
|
|
13576
|
+
handleEndTurn,
|
|
13577
|
+
handleCancel,
|
|
13578
|
+
handleReset,
|
|
13579
|
+
attackTargets,
|
|
13580
|
+
tileToScreen
|
|
13581
|
+
]
|
|
13582
|
+
);
|
|
13583
|
+
const shakeStyle = isShaking ? { animation: "battle-shake 0.3s ease-in-out" } : {};
|
|
13584
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("battle-board relative flex flex-col min-h-[600px] bg-[var(--color-background)]", className), children: [
|
|
13585
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
13586
|
+
@keyframes battle-shake {
|
|
13587
|
+
0%, 100% { transform: translate(0, 0); }
|
|
13588
|
+
10% { transform: translate(-3px, -2px); }
|
|
13589
|
+
20% { transform: translate(3px, 1px); }
|
|
13590
|
+
30% { transform: translate(-2px, 3px); }
|
|
13591
|
+
40% { transform: translate(2px, -1px); }
|
|
13592
|
+
50% { transform: translate(-3px, 2px); }
|
|
13593
|
+
60% { transform: translate(3px, -2px); }
|
|
13594
|
+
70% { transform: translate(-1px, 3px); }
|
|
13595
|
+
80% { transform: translate(2px, -3px); }
|
|
13596
|
+
90% { transform: translate(-2px, 1px); }
|
|
13597
|
+
}
|
|
13598
|
+
` }),
|
|
13599
|
+
header && /* @__PURE__ */ jsx("div", { className: "p-4", children: header(ctx) }),
|
|
13600
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 gap-4 p-4 pt-0", children: [
|
|
13601
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex-1", style: shakeStyle, children: [
|
|
13602
|
+
/* @__PURE__ */ jsx(
|
|
13603
|
+
IsometricCanvas_default,
|
|
13604
|
+
{
|
|
13605
|
+
tiles,
|
|
13606
|
+
units: isoUnits,
|
|
13607
|
+
features,
|
|
13608
|
+
selectedUnitId,
|
|
13609
|
+
validMoves,
|
|
13610
|
+
attackTargets,
|
|
13611
|
+
hoveredTile,
|
|
13612
|
+
onTileClick: handleTileClick,
|
|
13613
|
+
onUnitClick: handleUnitClick,
|
|
13614
|
+
onTileHover: (x, y) => setHoveredTile({ x, y }),
|
|
13615
|
+
onTileLeave: () => setHoveredTile(null),
|
|
13616
|
+
scale,
|
|
13617
|
+
assetBaseUrl: assetManifest?.baseUrl,
|
|
13618
|
+
assetManifest,
|
|
13619
|
+
backgroundImage,
|
|
13620
|
+
onDrawEffects,
|
|
13621
|
+
hasActiveEffects: hasActiveEffects2,
|
|
13622
|
+
effectSpriteUrls,
|
|
13623
|
+
resolveUnitFrame,
|
|
13624
|
+
unitScale
|
|
13625
|
+
}
|
|
13626
|
+
),
|
|
13627
|
+
overlay && overlay(ctx)
|
|
13628
|
+
] }),
|
|
13629
|
+
sidebar && /* @__PURE__ */ jsx("div", { className: "w-80 shrink-0", children: sidebar(ctx) })
|
|
13630
|
+
] }),
|
|
13631
|
+
actions ? actions(ctx) : currentPhase !== "game_over" && /* @__PURE__ */ jsxs("div", { className: "fixed bottom-6 right-6 z-50 flex gap-2", children: [
|
|
13632
|
+
(currentPhase === "movement" || currentPhase === "action") && /* @__PURE__ */ jsx(
|
|
13633
|
+
"button",
|
|
13634
|
+
{
|
|
13635
|
+
className: "px-4 py-2 rounded-lg bg-[var(--color-surface)] text-[var(--color-foreground)] border border-[var(--color-border)] shadow-xl hover:opacity-90",
|
|
13636
|
+
onClick: handleCancel,
|
|
13637
|
+
children: "Cancel"
|
|
13638
|
+
}
|
|
13639
|
+
),
|
|
13640
|
+
/* @__PURE__ */ jsx(
|
|
13641
|
+
"button",
|
|
13642
|
+
{
|
|
13643
|
+
className: "px-4 py-2 rounded-lg bg-[var(--color-primary)] text-white shadow-xl hover:opacity-90",
|
|
13644
|
+
onClick: handleEndTurn,
|
|
13645
|
+
children: "End Turn"
|
|
13646
|
+
}
|
|
13647
|
+
)
|
|
13648
|
+
] }),
|
|
13649
|
+
gameResult && (gameOverOverlay ? gameOverOverlay(ctx) : /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm rounded-xl", children: /* @__PURE__ */ jsxs("div", { className: "text-center space-y-6 p-8", children: [
|
|
13650
|
+
/* @__PURE__ */ jsx(
|
|
13651
|
+
"h2",
|
|
13652
|
+
{
|
|
13653
|
+
className: cn(
|
|
13654
|
+
"text-4xl font-black tracking-widest uppercase",
|
|
13655
|
+
gameResult === "victory" ? "text-yellow-400" : "text-red-500"
|
|
13656
|
+
),
|
|
13657
|
+
children: gameResult === "victory" ? "Victory!" : "Defeat"
|
|
13658
|
+
}
|
|
13659
|
+
),
|
|
13660
|
+
/* @__PURE__ */ jsxs("p", { className: "text-gray-300", children: [
|
|
13661
|
+
"Turns played: ",
|
|
13662
|
+
currentTurn
|
|
13663
|
+
] }),
|
|
13664
|
+
/* @__PURE__ */ jsx(
|
|
13665
|
+
"button",
|
|
13666
|
+
{
|
|
13667
|
+
className: "px-8 py-3 rounded-lg bg-[var(--color-primary)] text-white font-semibold hover:opacity-90",
|
|
13668
|
+
onClick: handleReset,
|
|
13669
|
+
children: "Play Again"
|
|
13670
|
+
}
|
|
13671
|
+
)
|
|
13672
|
+
] }) }))
|
|
13673
|
+
] });
|
|
13674
|
+
}
|
|
13675
|
+
BattleBoard.displayName = "BattleBoard";
|
|
13676
|
+
function defaultIsInRange(from, to, range) {
|
|
13677
|
+
return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
|
|
13678
|
+
}
|
|
13679
|
+
function WorldMapBoard({
|
|
13680
|
+
entity,
|
|
13681
|
+
scale = 0.4,
|
|
13682
|
+
unitScale = 2.5,
|
|
13683
|
+
allowMoveAllHeroes = false,
|
|
13684
|
+
isInRange = defaultIsInRange,
|
|
13685
|
+
heroSelectEvent,
|
|
13686
|
+
heroMoveEvent,
|
|
13687
|
+
battleEncounterEvent,
|
|
13688
|
+
featureEnterEvent,
|
|
13689
|
+
tileClickEvent,
|
|
13690
|
+
header,
|
|
13691
|
+
sidePanel,
|
|
13692
|
+
overlay,
|
|
13693
|
+
footer,
|
|
13694
|
+
onHeroSelect,
|
|
13695
|
+
onHeroMove,
|
|
13696
|
+
onBattleEncounter,
|
|
13697
|
+
onFeatureEnter,
|
|
13698
|
+
effectSpriteUrls = [],
|
|
13699
|
+
resolveUnitFrame,
|
|
13700
|
+
className
|
|
13701
|
+
}) {
|
|
13702
|
+
const eventBus = useEventBus();
|
|
13703
|
+
const hexes = entity.hexes;
|
|
13704
|
+
const heroes = entity.heroes;
|
|
13705
|
+
const features = entity.features ?? [];
|
|
13706
|
+
const selectedHeroId = entity.selectedHeroId;
|
|
13707
|
+
const assetManifest = entity.assetManifest;
|
|
13708
|
+
const backgroundImage = entity.backgroundImage;
|
|
13709
|
+
const [hoveredTile, setHoveredTile] = useState(null);
|
|
13710
|
+
const selectedHero = useMemo(
|
|
13711
|
+
() => heroes.find((h) => h.id === selectedHeroId) ?? null,
|
|
13712
|
+
[heroes, selectedHeroId]
|
|
13713
|
+
);
|
|
13714
|
+
const tiles = useMemo(
|
|
13715
|
+
() => hexes.map((hex) => ({
|
|
13716
|
+
x: hex.x,
|
|
13717
|
+
y: hex.y,
|
|
13718
|
+
terrain: hex.terrain,
|
|
13719
|
+
terrainSprite: hex.terrainSprite
|
|
13720
|
+
})),
|
|
13721
|
+
[hexes]
|
|
13722
|
+
);
|
|
13723
|
+
const baseUnits = useMemo(
|
|
13724
|
+
() => heroes.map((hero) => ({
|
|
13725
|
+
id: hero.id,
|
|
13726
|
+
position: hero.position,
|
|
13727
|
+
name: hero.name,
|
|
13728
|
+
team: hero.owner === "enemy" ? "enemy" : "player",
|
|
13729
|
+
health: 100,
|
|
13730
|
+
maxHealth: 100,
|
|
13731
|
+
sprite: hero.sprite
|
|
13732
|
+
})),
|
|
13733
|
+
[heroes]
|
|
13734
|
+
);
|
|
13735
|
+
const MOVE_SPEED_MS_PER_TILE = 300;
|
|
13736
|
+
const movementAnimRef = useRef(null);
|
|
13737
|
+
const [movingPositions, setMovingPositions] = useState(/* @__PURE__ */ new Map());
|
|
13738
|
+
const startMoveAnimation = useCallback((heroId, from, to, onComplete) => {
|
|
13739
|
+
const dist = Math.max(Math.abs(to.x - from.x), Math.abs(to.y - from.y));
|
|
13740
|
+
movementAnimRef.current = { heroId, from, to, elapsed: 0, duration: dist * MOVE_SPEED_MS_PER_TILE, onComplete };
|
|
13741
|
+
}, []);
|
|
13742
|
+
useEffect(() => {
|
|
13743
|
+
const interval = setInterval(() => {
|
|
13744
|
+
const anim2 = movementAnimRef.current;
|
|
13745
|
+
if (!anim2) return;
|
|
13746
|
+
anim2.elapsed += 16;
|
|
13747
|
+
const t = Math.min(anim2.elapsed / anim2.duration, 1);
|
|
13748
|
+
const eased = 1 - (1 - t) * (1 - t);
|
|
13749
|
+
const cx = anim2.from.x + (anim2.to.x - anim2.from.x) * eased;
|
|
13750
|
+
const cy = anim2.from.y + (anim2.to.y - anim2.from.y) * eased;
|
|
13751
|
+
if (t >= 1) {
|
|
13752
|
+
movementAnimRef.current = null;
|
|
13753
|
+
setMovingPositions((prev) => {
|
|
13754
|
+
const n = new Map(prev);
|
|
13755
|
+
n.delete(anim2.heroId);
|
|
13756
|
+
return n;
|
|
13757
|
+
});
|
|
13758
|
+
anim2.onComplete();
|
|
13759
|
+
} else {
|
|
13760
|
+
setMovingPositions((prev) => {
|
|
13761
|
+
const n = new Map(prev);
|
|
13762
|
+
n.set(anim2.heroId, { x: cx, y: cy });
|
|
13763
|
+
return n;
|
|
13764
|
+
});
|
|
13765
|
+
}
|
|
13766
|
+
}, 16);
|
|
13767
|
+
return () => clearInterval(interval);
|
|
13768
|
+
}, []);
|
|
13769
|
+
const isoUnits = useMemo(() => {
|
|
13770
|
+
if (movingPositions.size === 0) return baseUnits;
|
|
13771
|
+
return baseUnits.map((u) => {
|
|
13772
|
+
const pos = movingPositions.get(u.id);
|
|
13773
|
+
return pos ? { ...u, position: pos } : u;
|
|
13774
|
+
});
|
|
13775
|
+
}, [baseUnits, movingPositions]);
|
|
13776
|
+
const validMoves = useMemo(() => {
|
|
13777
|
+
if (!selectedHero || selectedHero.movement <= 0) return [];
|
|
13778
|
+
const moves = [];
|
|
13779
|
+
hexes.forEach((hex) => {
|
|
13780
|
+
if (hex.passable === false) return;
|
|
13781
|
+
if (hex.x === selectedHero.position.x && hex.y === selectedHero.position.y) return;
|
|
13782
|
+
if (!isInRange(selectedHero.position, { x: hex.x, y: hex.y }, selectedHero.movement)) return;
|
|
13783
|
+
if (heroes.some((h) => h.position.x === hex.x && h.position.y === hex.y && h.owner === selectedHero.owner)) return;
|
|
13784
|
+
moves.push({ x: hex.x, y: hex.y });
|
|
13785
|
+
});
|
|
13786
|
+
return moves;
|
|
13787
|
+
}, [selectedHero, hexes, heroes, isInRange]);
|
|
13788
|
+
const attackTargets = useMemo(() => {
|
|
13789
|
+
if (!selectedHero || selectedHero.movement <= 0) return [];
|
|
13790
|
+
return heroes.filter((h) => h.owner !== selectedHero.owner).filter((h) => isInRange(selectedHero.position, h.position, selectedHero.movement)).map((h) => h.position);
|
|
13791
|
+
}, [selectedHero, heroes, isInRange]);
|
|
13792
|
+
const maxY = Math.max(...hexes.map((h) => h.y), 0);
|
|
13793
|
+
const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
|
|
13794
|
+
const tileToScreen = useCallback(
|
|
13795
|
+
(tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
|
|
13796
|
+
[scale, baseOffsetX]
|
|
13797
|
+
);
|
|
13798
|
+
const hoveredHex = useMemo(
|
|
13799
|
+
() => hoveredTile ? hexes.find((h) => h.x === hoveredTile.x && h.y === hoveredTile.y) ?? null : null,
|
|
13800
|
+
[hoveredTile, hexes]
|
|
13801
|
+
);
|
|
13802
|
+
const hoveredHero = useMemo(
|
|
13803
|
+
() => hoveredTile ? heroes.find((h) => h.position.x === hoveredTile.x && h.position.y === hoveredTile.y) ?? null : null,
|
|
13804
|
+
[hoveredTile, heroes]
|
|
13805
|
+
);
|
|
13806
|
+
const handleTileClick = useCallback((x, y) => {
|
|
13807
|
+
if (movementAnimRef.current) return;
|
|
13808
|
+
const hex = hexes.find((h) => h.x === x && h.y === y);
|
|
13809
|
+
if (!hex) return;
|
|
13810
|
+
if (tileClickEvent) {
|
|
13811
|
+
eventBus.emit(`UI:${tileClickEvent}`, { x, y });
|
|
13812
|
+
}
|
|
13813
|
+
if (selectedHero && validMoves.some((m) => m.x === x && m.y === y)) {
|
|
13814
|
+
startMoveAnimation(selectedHero.id, { ...selectedHero.position }, { x, y }, () => {
|
|
13815
|
+
onHeroMove?.(selectedHero.id, x, y);
|
|
13816
|
+
if (heroMoveEvent) {
|
|
13817
|
+
eventBus.emit(`UI:${heroMoveEvent}`, { heroId: selectedHero.id, toX: x, toY: y });
|
|
13818
|
+
}
|
|
13819
|
+
if (hex.feature && hex.feature !== "none") {
|
|
13820
|
+
onFeatureEnter?.(selectedHero.id, hex);
|
|
13821
|
+
if (featureEnterEvent) {
|
|
13822
|
+
eventBus.emit(`UI:${featureEnterEvent}`, { heroId: selectedHero.id, feature: hex.feature, hex });
|
|
13823
|
+
}
|
|
13824
|
+
}
|
|
13825
|
+
});
|
|
13826
|
+
return;
|
|
13827
|
+
}
|
|
13828
|
+
const enemy = heroes.find((h) => h.position.x === x && h.position.y === y && h.owner === "enemy");
|
|
13829
|
+
if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
|
|
13830
|
+
onBattleEncounter?.(selectedHero.id, enemy.id);
|
|
13831
|
+
if (battleEncounterEvent) {
|
|
13832
|
+
eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId: selectedHero.id, defenderId: enemy.id });
|
|
13833
|
+
}
|
|
13834
|
+
}
|
|
13835
|
+
}, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
|
|
13836
|
+
const handleUnitClick = useCallback((unitId) => {
|
|
13837
|
+
const hero = heroes.find((h) => h.id === unitId);
|
|
13838
|
+
if (hero && (hero.owner === "player" || allowMoveAllHeroes)) {
|
|
13839
|
+
onHeroSelect?.(unitId);
|
|
13840
|
+
if (heroSelectEvent) {
|
|
13841
|
+
eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
|
|
13842
|
+
}
|
|
13843
|
+
}
|
|
13844
|
+
}, [heroes, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
|
|
13845
|
+
const selectHero = useCallback((id) => {
|
|
13846
|
+
onHeroSelect?.(id);
|
|
13847
|
+
if (heroSelectEvent) {
|
|
13848
|
+
eventBus.emit(`UI:${heroSelectEvent}`, { heroId: id });
|
|
13849
|
+
}
|
|
13850
|
+
}, [onHeroSelect, eventBus, heroSelectEvent]);
|
|
13851
|
+
const ctx = useMemo(
|
|
13852
|
+
() => ({
|
|
13853
|
+
hoveredTile,
|
|
13854
|
+
hoveredHex,
|
|
13855
|
+
hoveredHero,
|
|
13856
|
+
selectedHero,
|
|
13857
|
+
validMoves,
|
|
13858
|
+
selectHero,
|
|
13859
|
+
tileToScreen,
|
|
13860
|
+
scale
|
|
13861
|
+
}),
|
|
13862
|
+
[hoveredTile, hoveredHex, hoveredHero, selectedHero, validMoves, selectHero, tileToScreen, scale]
|
|
13863
|
+
);
|
|
13864
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("world-map-board min-h-screen flex flex-col bg-[var(--color-background)]", className), children: [
|
|
13865
|
+
header && header(ctx),
|
|
13866
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
|
|
13867
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-auto p-4 relative", children: [
|
|
13868
|
+
/* @__PURE__ */ jsx(
|
|
13869
|
+
IsometricCanvas_default,
|
|
13870
|
+
{
|
|
13871
|
+
tiles,
|
|
13872
|
+
units: isoUnits,
|
|
13873
|
+
features,
|
|
13874
|
+
selectedUnitId: selectedHeroId,
|
|
13875
|
+
validMoves,
|
|
13876
|
+
attackTargets,
|
|
13877
|
+
hoveredTile,
|
|
13878
|
+
onTileClick: handleTileClick,
|
|
13879
|
+
onUnitClick: handleUnitClick,
|
|
13880
|
+
onTileHover: (x, y) => setHoveredTile({ x, y }),
|
|
13881
|
+
onTileLeave: () => setHoveredTile(null),
|
|
13882
|
+
scale,
|
|
13883
|
+
assetBaseUrl: assetManifest?.baseUrl,
|
|
13884
|
+
assetManifest,
|
|
13885
|
+
backgroundImage,
|
|
13886
|
+
effectSpriteUrls,
|
|
13887
|
+
resolveUnitFrame,
|
|
13888
|
+
unitScale
|
|
13889
|
+
}
|
|
13890
|
+
),
|
|
13891
|
+
overlay && overlay(ctx)
|
|
13892
|
+
] }),
|
|
13893
|
+
sidePanel && /* @__PURE__ */ jsx("div", { className: "w-80 shrink-0 border-l border-[var(--color-border)] bg-[var(--color-surface)] overflow-y-auto p-4", children: sidePanel(ctx) })
|
|
13894
|
+
] }),
|
|
13895
|
+
footer && footer(ctx)
|
|
13896
|
+
] });
|
|
13897
|
+
}
|
|
13898
|
+
WorldMapBoard.displayName = "WorldMapBoard";
|
|
13899
|
+
function CastleBoard({
|
|
13900
|
+
entity,
|
|
13901
|
+
scale = 0.45,
|
|
13902
|
+
header,
|
|
13903
|
+
sidePanel,
|
|
13904
|
+
overlay,
|
|
13905
|
+
footer,
|
|
13906
|
+
onFeatureClick,
|
|
13907
|
+
onUnitClick,
|
|
13908
|
+
onTileClick,
|
|
13909
|
+
featureClickEvent,
|
|
13910
|
+
unitClickEvent,
|
|
13911
|
+
tileClickEvent,
|
|
13912
|
+
className
|
|
13913
|
+
}) {
|
|
13914
|
+
const eventBus = useEventBus();
|
|
13915
|
+
const tiles = entity.tiles;
|
|
13916
|
+
const features = entity.features ?? [];
|
|
13917
|
+
const units = entity.units ?? [];
|
|
13918
|
+
const assetManifest = entity.assetManifest;
|
|
13919
|
+
const backgroundImage = entity.backgroundImage;
|
|
13920
|
+
const [hoveredTile, setHoveredTile] = useState(null);
|
|
13921
|
+
const [selectedFeature, setSelectedFeature] = useState(null);
|
|
13922
|
+
const hoveredFeature = useMemo(() => {
|
|
13923
|
+
if (!hoveredTile) return null;
|
|
13924
|
+
return features.find((f) => f.x === hoveredTile.x && f.y === hoveredTile.y) ?? null;
|
|
13925
|
+
}, [hoveredTile, features]);
|
|
13926
|
+
const hoveredUnit = useMemo(() => {
|
|
13927
|
+
if (!hoveredTile) return null;
|
|
13928
|
+
return units.find(
|
|
13929
|
+
(u) => u.position?.x === hoveredTile.x && u.position?.y === hoveredTile.y
|
|
13930
|
+
) ?? null;
|
|
13931
|
+
}, [hoveredTile, units]);
|
|
13932
|
+
const maxY = Math.max(...tiles.map((t) => t.y), 0);
|
|
13933
|
+
const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
|
|
13934
|
+
const tileToScreen = useCallback(
|
|
13935
|
+
(tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
|
|
13936
|
+
[scale, baseOffsetX]
|
|
13937
|
+
);
|
|
13938
|
+
const handleTileClick = useCallback((x, y) => {
|
|
13939
|
+
const feature = features.find((f) => f.x === x && f.y === y);
|
|
13940
|
+
if (feature) {
|
|
13941
|
+
setSelectedFeature(feature);
|
|
13942
|
+
onFeatureClick?.(feature);
|
|
13943
|
+
if (featureClickEvent) {
|
|
13944
|
+
eventBus.emit(`UI:${featureClickEvent}`, {
|
|
13945
|
+
featureId: feature.id,
|
|
13946
|
+
featureType: feature.type,
|
|
13947
|
+
x: feature.x,
|
|
13948
|
+
y: feature.y
|
|
13949
|
+
});
|
|
13950
|
+
}
|
|
13951
|
+
}
|
|
13952
|
+
onTileClick?.(x, y);
|
|
13953
|
+
if (tileClickEvent) {
|
|
13954
|
+
eventBus.emit(`UI:${tileClickEvent}`, { x, y });
|
|
13955
|
+
}
|
|
13956
|
+
}, [features, onFeatureClick, onTileClick, featureClickEvent, tileClickEvent, eventBus]);
|
|
13957
|
+
const handleUnitClick = useCallback((unitId) => {
|
|
13958
|
+
const unit = units.find((u) => u.id === unitId);
|
|
13959
|
+
if (unit) {
|
|
13960
|
+
onUnitClick?.(unit);
|
|
13961
|
+
if (unitClickEvent) {
|
|
13962
|
+
eventBus.emit(`UI:${unitClickEvent}`, { unitId: unit.id });
|
|
13963
|
+
}
|
|
13964
|
+
}
|
|
13965
|
+
}, [units, onUnitClick, unitClickEvent, eventBus]);
|
|
13966
|
+
const clearSelection = useCallback(() => setSelectedFeature(null), []);
|
|
13967
|
+
const ctx = useMemo(
|
|
13968
|
+
() => ({
|
|
13969
|
+
hoveredTile,
|
|
13970
|
+
hoveredFeature,
|
|
13971
|
+
hoveredUnit,
|
|
13972
|
+
selectedFeature,
|
|
13973
|
+
clearSelection,
|
|
13974
|
+
tileToScreen,
|
|
13975
|
+
scale
|
|
13976
|
+
}),
|
|
13977
|
+
[hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale]
|
|
13978
|
+
);
|
|
13979
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("castle-board min-h-screen flex flex-col bg-[var(--color-background)]", className), children: [
|
|
13980
|
+
header && header(ctx),
|
|
13981
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
|
|
13982
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-auto p-4 relative", children: [
|
|
13983
|
+
/* @__PURE__ */ jsx(
|
|
13984
|
+
IsometricCanvas_default,
|
|
13985
|
+
{
|
|
13986
|
+
tiles,
|
|
13987
|
+
units,
|
|
13988
|
+
features,
|
|
13989
|
+
hoveredTile,
|
|
13990
|
+
onTileClick: handleTileClick,
|
|
13991
|
+
onUnitClick: handleUnitClick,
|
|
13992
|
+
onTileHover: (x, y) => setHoveredTile({ x, y }),
|
|
13993
|
+
onTileLeave: () => setHoveredTile(null),
|
|
13994
|
+
scale,
|
|
13995
|
+
assetBaseUrl: assetManifest?.baseUrl,
|
|
13996
|
+
assetManifest,
|
|
13997
|
+
backgroundImage
|
|
13998
|
+
}
|
|
13999
|
+
),
|
|
14000
|
+
overlay && overlay(ctx)
|
|
14001
|
+
] }),
|
|
14002
|
+
sidePanel && /* @__PURE__ */ jsx("div", { className: "w-96 shrink-0 border-l border-[var(--color-border)] bg-[var(--color-surface)] overflow-y-auto", children: sidePanel(ctx) })
|
|
14003
|
+
] }),
|
|
14004
|
+
footer && footer(ctx)
|
|
14005
|
+
] });
|
|
14006
|
+
}
|
|
14007
|
+
CastleBoard.displayName = "CastleBoard";
|
|
14008
|
+
var TERRAIN_COLORS = {
|
|
14009
|
+
grass: "#4a7c3f",
|
|
14010
|
+
dirt: "#8b6c42",
|
|
14011
|
+
stone: "#7a7a7a",
|
|
14012
|
+
sand: "#c4a84d",
|
|
14013
|
+
water: "#3a6ea5",
|
|
14014
|
+
forest: "#2d5a1e",
|
|
14015
|
+
mountain: "#5a4a3a",
|
|
14016
|
+
lava: "#c44b2b",
|
|
14017
|
+
ice: "#a0d2e8",
|
|
14018
|
+
plains: "#6b8e4e",
|
|
14019
|
+
fortress: "#4a4a5a",
|
|
14020
|
+
castle: "#5a5a6a"
|
|
14021
|
+
};
|
|
14022
|
+
var FEATURE_TYPES = [
|
|
14023
|
+
"goldMine",
|
|
14024
|
+
"resonanceCrystal",
|
|
14025
|
+
"traitCache",
|
|
14026
|
+
"salvageYard",
|
|
14027
|
+
"portal",
|
|
14028
|
+
"battleMarker",
|
|
14029
|
+
"treasure",
|
|
14030
|
+
"castle"
|
|
14031
|
+
];
|
|
14032
|
+
function CollapsibleSection({ title, expanded, onToggle, children, className }) {
|
|
14033
|
+
const Icon2 = expanded ? ChevronDown : ChevronRight;
|
|
14034
|
+
return /* @__PURE__ */ jsxs(VStack, { gap: "xs", className, children: [
|
|
14035
|
+
/* @__PURE__ */ jsx(
|
|
14036
|
+
Button,
|
|
14037
|
+
{
|
|
14038
|
+
variant: "ghost",
|
|
14039
|
+
size: "sm",
|
|
14040
|
+
onClick: onToggle,
|
|
14041
|
+
className: "w-full justify-start text-left",
|
|
14042
|
+
children: /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
|
|
14043
|
+
/* @__PURE__ */ jsx(Icon2, { size: 14 }),
|
|
14044
|
+
/* @__PURE__ */ jsx(Typography, { variant: "label", weight: "semibold", children: title })
|
|
14045
|
+
] })
|
|
14046
|
+
}
|
|
14047
|
+
),
|
|
14048
|
+
expanded && /* @__PURE__ */ jsx(Box, { padding: "xs", paddingX: "sm", children })
|
|
14049
|
+
] });
|
|
14050
|
+
}
|
|
14051
|
+
CollapsibleSection.displayName = "CollapsibleSection";
|
|
14052
|
+
function EditorSlider({ label, value, min, max, step = 0.1, onChange, className }) {
|
|
14053
|
+
return /* @__PURE__ */ jsxs(HStack, { gap: "sm", align: "center", className, children: [
|
|
14054
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "min-w-[80px] text-gray-300", children: label }),
|
|
14055
|
+
/* @__PURE__ */ jsx(Box, { className: "flex-1", children: /* @__PURE__ */ jsx(
|
|
14056
|
+
"input",
|
|
14057
|
+
{
|
|
14058
|
+
type: "range",
|
|
14059
|
+
min,
|
|
14060
|
+
max,
|
|
14061
|
+
step,
|
|
14062
|
+
value,
|
|
14063
|
+
onChange: (e) => onChange(parseFloat(e.target.value)),
|
|
14064
|
+
className: "w-full h-1.5 bg-gray-600 rounded-lg appearance-none cursor-pointer accent-blue-500"
|
|
14065
|
+
}
|
|
14066
|
+
) }),
|
|
14067
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "min-w-[40px] text-right text-gray-400", children: typeof step === "number" && step < 1 ? value.toFixed(1) : value })
|
|
14068
|
+
] });
|
|
14069
|
+
}
|
|
14070
|
+
EditorSlider.displayName = "EditorSlider";
|
|
14071
|
+
function EditorSelect({ label, value, options, onChange, className }) {
|
|
14072
|
+
return /* @__PURE__ */ jsxs(HStack, { gap: "sm", align: "center", className, children: [
|
|
14073
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "min-w-[80px] text-gray-300", children: label }),
|
|
14074
|
+
/* @__PURE__ */ jsx(Box, { className: "flex-1", children: /* @__PURE__ */ jsx(
|
|
14075
|
+
"select",
|
|
14076
|
+
{
|
|
14077
|
+
value,
|
|
14078
|
+
onChange: (e) => onChange(e.target.value),
|
|
14079
|
+
className: "w-full px-2 py-1 text-xs bg-gray-700 text-gray-200 border border-gray-600 rounded cursor-pointer",
|
|
14080
|
+
children: options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
|
|
14081
|
+
}
|
|
14082
|
+
) })
|
|
14083
|
+
] });
|
|
14084
|
+
}
|
|
14085
|
+
EditorSelect.displayName = "EditorSelect";
|
|
14086
|
+
function EditorCheckbox({ label, checked, onChange, className }) {
|
|
14087
|
+
return /* @__PURE__ */ jsxs(HStack, { gap: "sm", align: "center", className, children: [
|
|
14088
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "min-w-[80px] text-gray-300", children: label }),
|
|
14089
|
+
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(
|
|
14090
|
+
"input",
|
|
14091
|
+
{
|
|
14092
|
+
type: "checkbox",
|
|
14093
|
+
checked,
|
|
14094
|
+
onChange: (e) => onChange(e.target.checked),
|
|
14095
|
+
className: "w-4 h-4 accent-blue-500 cursor-pointer"
|
|
14096
|
+
}
|
|
14097
|
+
) })
|
|
14098
|
+
] });
|
|
14099
|
+
}
|
|
14100
|
+
EditorCheckbox.displayName = "EditorCheckbox";
|
|
14101
|
+
function EditorTextInput({ label, value, onChange, placeholder, className }) {
|
|
14102
|
+
return /* @__PURE__ */ jsxs(HStack, { gap: "sm", align: "center", className, children: [
|
|
14103
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "min-w-[80px] text-gray-300", children: label }),
|
|
14104
|
+
/* @__PURE__ */ jsx(Box, { className: "flex-1", children: /* @__PURE__ */ jsx(
|
|
14105
|
+
"input",
|
|
14106
|
+
{
|
|
14107
|
+
type: "text",
|
|
14108
|
+
value,
|
|
14109
|
+
onChange: (e) => onChange(e.target.value),
|
|
14110
|
+
placeholder,
|
|
14111
|
+
className: "w-full px-2 py-1 text-xs bg-gray-700 text-gray-200 border border-gray-600 rounded"
|
|
14112
|
+
}
|
|
14113
|
+
) })
|
|
14114
|
+
] });
|
|
14115
|
+
}
|
|
14116
|
+
EditorTextInput.displayName = "EditorTextInput";
|
|
14117
|
+
function StatusBar({ hoveredTile, mode, gridSize, unitCount, featureCount, className }) {
|
|
14118
|
+
return /* @__PURE__ */ jsxs(HStack, { gap: "sm", align: "center", className: `px-3 py-1.5 bg-gray-800 border-t border-gray-700 ${className ?? ""}`, children: [
|
|
14119
|
+
/* @__PURE__ */ jsx(Badge, { variant: "info", size: "sm", children: mode }),
|
|
14120
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-gray-400", children: [
|
|
14121
|
+
"Tile: ",
|
|
14122
|
+
hoveredTile ? `(${hoveredTile.x}, ${hoveredTile.y})` : "\u2014"
|
|
14123
|
+
] }),
|
|
14124
|
+
gridSize && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-gray-500", children: [
|
|
14125
|
+
"Grid: ",
|
|
14126
|
+
gridSize.width,
|
|
14127
|
+
"x",
|
|
14128
|
+
gridSize.height
|
|
14129
|
+
] }),
|
|
14130
|
+
unitCount !== void 0 && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-gray-500", children: [
|
|
14131
|
+
"Units: ",
|
|
14132
|
+
unitCount
|
|
14133
|
+
] }),
|
|
14134
|
+
featureCount !== void 0 && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-gray-500", children: [
|
|
14135
|
+
"Features: ",
|
|
14136
|
+
featureCount
|
|
14137
|
+
] })
|
|
14138
|
+
] });
|
|
14139
|
+
}
|
|
14140
|
+
StatusBar.displayName = "StatusBar";
|
|
14141
|
+
function TerrainPalette({ terrains, selectedTerrain, onSelect, className }) {
|
|
14142
|
+
return /* @__PURE__ */ jsx(HStack, { gap: "xs", wrap: true, className, children: terrains.map((terrain) => /* @__PURE__ */ jsx(
|
|
14143
|
+
Box,
|
|
14144
|
+
{
|
|
14145
|
+
onClick: () => onSelect(terrain),
|
|
14146
|
+
className: `w-8 h-8 rounded cursor-pointer border-2 transition-all ${selectedTerrain === terrain ? "border-white scale-110 shadow-lg" : "border-gray-600 hover:border-gray-400"}`,
|
|
14147
|
+
style: { backgroundColor: TERRAIN_COLORS[terrain] || "#555" },
|
|
14148
|
+
title: terrain
|
|
14149
|
+
},
|
|
14150
|
+
terrain
|
|
14151
|
+
)) });
|
|
14152
|
+
}
|
|
14153
|
+
TerrainPalette.displayName = "TerrainPalette";
|
|
14154
|
+
var MODE_LABELS = {
|
|
14155
|
+
select: "Select",
|
|
14156
|
+
paint: "Paint",
|
|
14157
|
+
unit: "Unit",
|
|
14158
|
+
feature: "Feature",
|
|
14159
|
+
erase: "Erase"
|
|
14160
|
+
};
|
|
14161
|
+
function EditorToolbar({ mode, onModeChange, className }) {
|
|
14162
|
+
const modes = ["select", "paint", "unit", "feature", "erase"];
|
|
14163
|
+
return /* @__PURE__ */ jsx(HStack, { gap: "xs", wrap: true, className, children: modes.map((m) => /* @__PURE__ */ jsx(
|
|
14164
|
+
Button,
|
|
14165
|
+
{
|
|
14166
|
+
variant: mode === m ? "primary" : "ghost",
|
|
14167
|
+
size: "sm",
|
|
14168
|
+
onClick: () => onModeChange(m),
|
|
14169
|
+
children: MODE_LABELS[m]
|
|
14170
|
+
},
|
|
14171
|
+
m
|
|
14172
|
+
)) });
|
|
14173
|
+
}
|
|
14174
|
+
EditorToolbar.displayName = "EditorToolbar";
|
|
14175
|
+
function VStackPattern({
|
|
14176
|
+
gap = "md",
|
|
14177
|
+
align = "stretch",
|
|
14178
|
+
justify = "start",
|
|
14179
|
+
className,
|
|
14180
|
+
style,
|
|
14181
|
+
children
|
|
14182
|
+
}) {
|
|
14183
|
+
return /* @__PURE__ */ jsx(VStack, { gap, align, justify, className, style, children });
|
|
14184
|
+
}
|
|
14185
|
+
VStackPattern.displayName = "VStackPattern";
|
|
14186
|
+
function HStackPattern({
|
|
14187
|
+
gap = "md",
|
|
14188
|
+
align = "center",
|
|
14189
|
+
justify = "start",
|
|
14190
|
+
wrap = false,
|
|
14191
|
+
className,
|
|
14192
|
+
style,
|
|
14193
|
+
children
|
|
14194
|
+
}) {
|
|
14195
|
+
return /* @__PURE__ */ jsx(HStack, { gap, align, justify, wrap, className, style, children });
|
|
14196
|
+
}
|
|
14197
|
+
HStackPattern.displayName = "HStackPattern";
|
|
14198
|
+
function BoxPattern({
|
|
14199
|
+
p: p2,
|
|
14200
|
+
m,
|
|
14201
|
+
bg = "transparent",
|
|
14202
|
+
border = false,
|
|
14203
|
+
radius = "none",
|
|
14204
|
+
shadow = "none",
|
|
14205
|
+
className,
|
|
14206
|
+
style,
|
|
14207
|
+
children
|
|
14208
|
+
}) {
|
|
14209
|
+
return /* @__PURE__ */ jsx(
|
|
14210
|
+
Box,
|
|
14211
|
+
{
|
|
14212
|
+
padding: p2,
|
|
14213
|
+
margin: m,
|
|
14214
|
+
bg,
|
|
14215
|
+
border,
|
|
14216
|
+
rounded: radius,
|
|
14217
|
+
shadow,
|
|
14218
|
+
className,
|
|
14219
|
+
style,
|
|
14220
|
+
children
|
|
14221
|
+
}
|
|
14222
|
+
);
|
|
14223
|
+
}
|
|
14224
|
+
BoxPattern.displayName = "BoxPattern";
|
|
14225
|
+
function GridPattern({
|
|
14226
|
+
cols = 1,
|
|
14227
|
+
gap = "md",
|
|
14228
|
+
rowGap,
|
|
14229
|
+
colGap,
|
|
14230
|
+
className,
|
|
14231
|
+
style,
|
|
14232
|
+
children
|
|
14233
|
+
}) {
|
|
14234
|
+
return /* @__PURE__ */ jsx(Grid2, { cols, gap, rowGap, colGap, className, style, children });
|
|
14235
|
+
}
|
|
14236
|
+
GridPattern.displayName = "GridPattern";
|
|
14237
|
+
function CenterPattern({
|
|
14238
|
+
minHeight,
|
|
14239
|
+
className,
|
|
14240
|
+
style,
|
|
14241
|
+
children
|
|
14242
|
+
}) {
|
|
14243
|
+
const mergedStyle = minHeight ? { minHeight, ...style } : style;
|
|
14244
|
+
return /* @__PURE__ */ jsx(Center, { className, style: mergedStyle, children });
|
|
14245
|
+
}
|
|
14246
|
+
CenterPattern.displayName = "CenterPattern";
|
|
14247
|
+
function SpacerPattern({ size = "flex" }) {
|
|
14248
|
+
if (size === "flex") {
|
|
14249
|
+
return /* @__PURE__ */ jsx(Spacer, {});
|
|
14250
|
+
}
|
|
14251
|
+
const sizeMap5 = {
|
|
14252
|
+
xs: "0.25rem",
|
|
14253
|
+
sm: "0.5rem",
|
|
14254
|
+
md: "1rem",
|
|
14255
|
+
lg: "1.5rem",
|
|
14256
|
+
xl: "2rem"
|
|
14257
|
+
};
|
|
14258
|
+
return /* @__PURE__ */ jsx("div", { style: { width: sizeMap5[size], height: sizeMap5[size], flexShrink: 0 } });
|
|
14259
|
+
}
|
|
14260
|
+
SpacerPattern.displayName = "SpacerPattern";
|
|
14261
|
+
function DividerPattern({
|
|
14262
|
+
orientation = "horizontal",
|
|
14263
|
+
variant = "solid",
|
|
14264
|
+
spacing = "md"
|
|
14265
|
+
}) {
|
|
14266
|
+
const spacingMap = {
|
|
14267
|
+
xs: "my-1",
|
|
14268
|
+
sm: "my-2",
|
|
14269
|
+
md: "my-4",
|
|
14270
|
+
lg: "my-6"
|
|
14271
|
+
};
|
|
14272
|
+
const verticalSpacingMap = {
|
|
14273
|
+
xs: "mx-1",
|
|
14274
|
+
sm: "mx-2",
|
|
14275
|
+
md: "mx-4",
|
|
14276
|
+
lg: "mx-6"
|
|
14277
|
+
};
|
|
14278
|
+
return /* @__PURE__ */ jsx(
|
|
14279
|
+
Divider,
|
|
14280
|
+
{
|
|
14281
|
+
orientation,
|
|
14282
|
+
variant,
|
|
14283
|
+
className: orientation === "horizontal" ? spacingMap[spacing] : verticalSpacingMap[spacing]
|
|
14284
|
+
}
|
|
14285
|
+
);
|
|
14286
|
+
}
|
|
14287
|
+
DividerPattern.displayName = "DividerPattern";
|
|
14288
|
+
function ButtonPattern({
|
|
14289
|
+
label,
|
|
14290
|
+
variant = "primary",
|
|
14291
|
+
size = "md",
|
|
14292
|
+
disabled = false,
|
|
14293
|
+
onClick,
|
|
14294
|
+
icon,
|
|
14295
|
+
iconPosition = "left",
|
|
14296
|
+
className
|
|
14297
|
+
}) {
|
|
14298
|
+
const { emit } = useEventBus();
|
|
14299
|
+
const handleClick = () => {
|
|
14300
|
+
if (onClick && !disabled) {
|
|
14301
|
+
emit(`UI:${onClick}`, {});
|
|
14302
|
+
}
|
|
14303
|
+
};
|
|
14304
|
+
return /* @__PURE__ */ jsxs(
|
|
14305
|
+
Button,
|
|
14306
|
+
{
|
|
14307
|
+
variant,
|
|
14308
|
+
size,
|
|
14309
|
+
disabled,
|
|
14310
|
+
onClick: handleClick,
|
|
14311
|
+
className,
|
|
14312
|
+
children: [
|
|
14313
|
+
icon && iconPosition === "left" && /* @__PURE__ */ jsx(Icon, { name: icon, size: "sm" }),
|
|
14314
|
+
label,
|
|
14315
|
+
icon && iconPosition === "right" && /* @__PURE__ */ jsx(Icon, { name: icon, size: "sm" })
|
|
14316
|
+
]
|
|
14317
|
+
}
|
|
14318
|
+
);
|
|
14319
|
+
}
|
|
14320
|
+
ButtonPattern.displayName = "ButtonPattern";
|
|
14321
|
+
function IconButtonPattern({
|
|
14322
|
+
icon,
|
|
14323
|
+
variant = "ghost",
|
|
14324
|
+
size = "md",
|
|
14325
|
+
onClick,
|
|
14326
|
+
ariaLabel,
|
|
14327
|
+
className
|
|
14328
|
+
}) {
|
|
14329
|
+
const { emit } = useEventBus();
|
|
14330
|
+
const handleClick = () => {
|
|
14331
|
+
if (onClick) {
|
|
14332
|
+
emit(`UI:${onClick}`, {});
|
|
14333
|
+
}
|
|
14334
|
+
};
|
|
14335
|
+
return /* @__PURE__ */ jsx(
|
|
14336
|
+
Button,
|
|
14337
|
+
{
|
|
14338
|
+
variant,
|
|
14339
|
+
size,
|
|
14340
|
+
onClick: handleClick,
|
|
13150
14341
|
"aria-label": ariaLabel,
|
|
13151
14342
|
className,
|
|
13152
14343
|
children: /* @__PURE__ */ jsx(Icon, { name: icon, size: size === "sm" ? "sm" : "md" })
|
|
@@ -17637,707 +18828,164 @@ var GenericAppTemplate = ({
|
|
|
17637
18828
|
{
|
|
17638
18829
|
padding: "md",
|
|
17639
18830
|
border: true,
|
|
17640
|
-
className: "border-b-2 border-x-0 border-t-0 border-[var(--color-border)] flex items-center justify-between flex-shrink-0",
|
|
17641
|
-
children: [
|
|
17642
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
17643
|
-
/* @__PURE__ */ jsx(Typography, { variant: "h3", children: title }),
|
|
17644
|
-
subtitle && /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "secondary", className: "mt-1", children: subtitle })
|
|
17645
|
-
] }),
|
|
17646
|
-
headerActions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: headerActions })
|
|
17647
|
-
]
|
|
17648
|
-
}
|
|
17649
|
-
),
|
|
17650
|
-
/* @__PURE__ */ jsx(Box, { fullWidth: true, overflow: "auto", className: "flex-1", children: /* @__PURE__ */ jsx(Box, { padding: "lg", children }) }),
|
|
17651
|
-
footer && /* @__PURE__ */ jsx(
|
|
17652
|
-
Box,
|
|
17653
|
-
{
|
|
17654
|
-
padding: "md",
|
|
17655
|
-
border: true,
|
|
17656
|
-
bg: "muted",
|
|
17657
|
-
className: "border-t-2 border-x-0 border-b-0 border-[var(--color-border)] flex-shrink-0",
|
|
17658
|
-
children: footer
|
|
17659
|
-
}
|
|
17660
|
-
)
|
|
17661
|
-
] });
|
|
17662
|
-
};
|
|
17663
|
-
GenericAppTemplate.displayName = "GenericAppTemplate";
|
|
17664
|
-
var GameShell = ({
|
|
17665
|
-
appName = "Game",
|
|
17666
|
-
hud,
|
|
17667
|
-
className,
|
|
17668
|
-
showTopBar = true
|
|
17669
|
-
}) => {
|
|
17670
|
-
return /* @__PURE__ */ jsxs(
|
|
17671
|
-
"div",
|
|
17672
|
-
{
|
|
17673
|
-
className: cn(
|
|
17674
|
-
"game-shell",
|
|
17675
|
-
"flex flex-col w-full h-screen overflow-hidden",
|
|
17676
|
-
className
|
|
17677
|
-
),
|
|
17678
|
-
style: {
|
|
17679
|
-
width: "100vw",
|
|
17680
|
-
height: "100vh",
|
|
17681
|
-
display: "flex",
|
|
17682
|
-
flexDirection: "column",
|
|
17683
|
-
overflow: "hidden",
|
|
17684
|
-
background: "var(--color-background, #0a0a0f)",
|
|
17685
|
-
color: "var(--color-text, #e0e0e0)"
|
|
17686
|
-
},
|
|
17687
|
-
children: [
|
|
17688
|
-
showTopBar && /* @__PURE__ */ jsxs(
|
|
17689
|
-
"header",
|
|
17690
|
-
{
|
|
17691
|
-
className: "game-shell__header",
|
|
17692
|
-
style: {
|
|
17693
|
-
display: "flex",
|
|
17694
|
-
alignItems: "center",
|
|
17695
|
-
justifyContent: "space-between",
|
|
17696
|
-
padding: "0.5rem 1rem",
|
|
17697
|
-
borderBottom: "1px solid var(--color-border, #2a2a3a)",
|
|
17698
|
-
background: "var(--color-surface, #12121f)",
|
|
17699
|
-
flexShrink: 0
|
|
17700
|
-
},
|
|
17701
|
-
children: [
|
|
17702
|
-
/* @__PURE__ */ jsx(
|
|
17703
|
-
"span",
|
|
17704
|
-
{
|
|
17705
|
-
style: {
|
|
17706
|
-
fontWeight: 700,
|
|
17707
|
-
fontSize: "1.1rem",
|
|
17708
|
-
letterSpacing: "0.02em"
|
|
17709
|
-
},
|
|
17710
|
-
children: appName
|
|
17711
|
-
}
|
|
17712
|
-
),
|
|
17713
|
-
hud && /* @__PURE__ */ jsx("div", { className: "game-shell__hud", children: hud })
|
|
17714
|
-
]
|
|
17715
|
-
}
|
|
17716
|
-
),
|
|
17717
|
-
/* @__PURE__ */ jsx(
|
|
17718
|
-
"main",
|
|
17719
|
-
{
|
|
17720
|
-
className: "game-shell__content",
|
|
17721
|
-
style: {
|
|
17722
|
-
flex: 1,
|
|
17723
|
-
overflow: "hidden",
|
|
17724
|
-
position: "relative"
|
|
17725
|
-
},
|
|
17726
|
-
children: /* @__PURE__ */ jsx(Outlet, {})
|
|
17727
|
-
}
|
|
17728
|
-
)
|
|
17729
|
-
]
|
|
17730
|
-
}
|
|
17731
|
-
);
|
|
17732
|
-
};
|
|
17733
|
-
GameShell.displayName = "GameShell";
|
|
17734
|
-
function BattleTemplate({
|
|
17735
|
-
initialUnits,
|
|
17736
|
-
tiles,
|
|
17737
|
-
scale = 0.45,
|
|
17738
|
-
boardWidth = 8,
|
|
17739
|
-
boardHeight = 6,
|
|
17740
|
-
assetManifest,
|
|
17741
|
-
backgroundImage,
|
|
17742
|
-
unitScale = 1,
|
|
17743
|
-
header,
|
|
17744
|
-
sidebar,
|
|
17745
|
-
actions,
|
|
17746
|
-
overlay,
|
|
17747
|
-
gameOverOverlay,
|
|
17748
|
-
features = [],
|
|
17749
|
-
onAttack,
|
|
17750
|
-
onGameEnd,
|
|
17751
|
-
onUnitMove,
|
|
17752
|
-
calculateDamage,
|
|
17753
|
-
onDrawEffects,
|
|
17754
|
-
hasActiveEffects: hasActiveEffects2 = false,
|
|
17755
|
-
effectSpriteUrls = [],
|
|
17756
|
-
resolveUnitFrame,
|
|
17757
|
-
className
|
|
17758
|
-
}) {
|
|
17759
|
-
const [units, setUnits] = useState(initialUnits);
|
|
17760
|
-
const [selectedUnitId, setSelectedUnitId] = useState(null);
|
|
17761
|
-
const [hoveredTile, setHoveredTile] = useState(null);
|
|
17762
|
-
const [currentPhase, setCurrentPhase] = useState("observation");
|
|
17763
|
-
const [currentTurn, setCurrentTurn] = useState(1);
|
|
17764
|
-
const [gameResult, setGameResult] = useState(null);
|
|
17765
|
-
const [isShaking, setIsShaking] = useState(false);
|
|
17766
|
-
const selectedUnit = useMemo(
|
|
17767
|
-
() => units.find((u) => u.id === selectedUnitId) ?? null,
|
|
17768
|
-
[units, selectedUnitId]
|
|
17769
|
-
);
|
|
17770
|
-
const hoveredUnit = useMemo(() => {
|
|
17771
|
-
if (!hoveredTile) return null;
|
|
17772
|
-
return units.find(
|
|
17773
|
-
(u) => u.position.x === hoveredTile.x && u.position.y === hoveredTile.y && u.health > 0
|
|
17774
|
-
) ?? null;
|
|
17775
|
-
}, [hoveredTile, units]);
|
|
17776
|
-
const playerUnits = useMemo(() => units.filter((u) => u.team === "player" && u.health > 0), [units]);
|
|
17777
|
-
const enemyUnits = useMemo(() => units.filter((u) => u.team === "enemy" && u.health > 0), [units]);
|
|
17778
|
-
const validMoves = useMemo(() => {
|
|
17779
|
-
if (!selectedUnit || currentPhase !== "movement") return [];
|
|
17780
|
-
const moves = [];
|
|
17781
|
-
const range = selectedUnit.movement;
|
|
17782
|
-
for (let dy = -range; dy <= range; dy++) {
|
|
17783
|
-
for (let dx = -range; dx <= range; dx++) {
|
|
17784
|
-
const nx = selectedUnit.position.x + dx;
|
|
17785
|
-
const ny = selectedUnit.position.y + dy;
|
|
17786
|
-
const dist = Math.abs(dx) + Math.abs(dy);
|
|
17787
|
-
if (dist > 0 && dist <= range && nx >= 0 && nx < boardWidth && ny >= 0 && ny < boardHeight && !units.some((u) => u.position.x === nx && u.position.y === ny && u.health > 0)) {
|
|
17788
|
-
moves.push({ x: nx, y: ny });
|
|
17789
|
-
}
|
|
17790
|
-
}
|
|
17791
|
-
}
|
|
17792
|
-
return moves;
|
|
17793
|
-
}, [selectedUnit, currentPhase, units, boardWidth, boardHeight]);
|
|
17794
|
-
const attackTargets = useMemo(() => {
|
|
17795
|
-
if (!selectedUnit || currentPhase !== "action") return [];
|
|
17796
|
-
return units.filter((u) => u.team !== selectedUnit.team && u.health > 0).filter((u) => {
|
|
17797
|
-
const dx = Math.abs(u.position.x - selectedUnit.position.x);
|
|
17798
|
-
const dy = Math.abs(u.position.y - selectedUnit.position.y);
|
|
17799
|
-
return dx <= 1 && dy <= 1 && dx + dy > 0;
|
|
17800
|
-
}).map((u) => u.position);
|
|
17801
|
-
}, [selectedUnit, currentPhase, units]);
|
|
17802
|
-
const MOVE_SPEED_MS_PER_TILE = 300;
|
|
17803
|
-
const movementAnimRef = useRef(null);
|
|
17804
|
-
const [movingPositions, setMovingPositions] = useState(/* @__PURE__ */ new Map());
|
|
17805
|
-
const startMoveAnimation = useCallback((unitId, from, to, onComplete) => {
|
|
17806
|
-
const dx = to.x - from.x;
|
|
17807
|
-
const dy = to.y - from.y;
|
|
17808
|
-
const dist = Math.max(Math.abs(dx), Math.abs(dy));
|
|
17809
|
-
const duration = dist * MOVE_SPEED_MS_PER_TILE;
|
|
17810
|
-
movementAnimRef.current = { unitId, from, to, elapsed: 0, duration, onComplete };
|
|
17811
|
-
}, []);
|
|
17812
|
-
useEffect(() => {
|
|
17813
|
-
const interval = setInterval(() => {
|
|
17814
|
-
const anim2 = movementAnimRef.current;
|
|
17815
|
-
if (!anim2) return;
|
|
17816
|
-
anim2.elapsed += 16;
|
|
17817
|
-
const t = Math.min(anim2.elapsed / anim2.duration, 1);
|
|
17818
|
-
const eased = 1 - (1 - t) * (1 - t);
|
|
17819
|
-
const cx = anim2.from.x + (anim2.to.x - anim2.from.x) * eased;
|
|
17820
|
-
const cy = anim2.from.y + (anim2.to.y - anim2.from.y) * eased;
|
|
17821
|
-
if (t >= 1) {
|
|
17822
|
-
movementAnimRef.current = null;
|
|
17823
|
-
setMovingPositions((prev) => {
|
|
17824
|
-
const next = new Map(prev);
|
|
17825
|
-
next.delete(anim2.unitId);
|
|
17826
|
-
return next;
|
|
17827
|
-
});
|
|
17828
|
-
anim2.onComplete();
|
|
17829
|
-
} else {
|
|
17830
|
-
setMovingPositions((prev) => {
|
|
17831
|
-
const next = new Map(prev);
|
|
17832
|
-
next.set(anim2.unitId, { x: cx, y: cy });
|
|
17833
|
-
return next;
|
|
17834
|
-
});
|
|
17835
|
-
}
|
|
17836
|
-
}, 16);
|
|
17837
|
-
return () => clearInterval(interval);
|
|
17838
|
-
}, []);
|
|
17839
|
-
const isoUnits = useMemo(() => {
|
|
17840
|
-
return units.filter((u) => u.health > 0).map((unit) => {
|
|
17841
|
-
const pos = movingPositions.get(unit.id) ?? unit.position;
|
|
17842
|
-
return {
|
|
17843
|
-
id: unit.id,
|
|
17844
|
-
position: pos,
|
|
17845
|
-
name: unit.name,
|
|
17846
|
-
team: unit.team,
|
|
17847
|
-
health: unit.health,
|
|
17848
|
-
maxHealth: unit.maxHealth,
|
|
17849
|
-
unitType: unit.unitType,
|
|
17850
|
-
heroId: unit.heroId,
|
|
17851
|
-
sprite: unit.sprite,
|
|
17852
|
-
traits: unit.traits?.map((t) => ({
|
|
17853
|
-
name: t.name,
|
|
17854
|
-
currentState: t.currentState,
|
|
17855
|
-
states: t.states,
|
|
17856
|
-
cooldown: t.cooldown ?? 0
|
|
17857
|
-
}))
|
|
17858
|
-
};
|
|
17859
|
-
});
|
|
17860
|
-
}, [units, movingPositions]);
|
|
17861
|
-
const maxY = Math.max(...tiles.map((t) => t.y), 0);
|
|
17862
|
-
const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
|
|
17863
|
-
const tileToScreen = useCallback(
|
|
17864
|
-
(tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
|
|
17865
|
-
[scale, baseOffsetX]
|
|
17866
|
-
);
|
|
17867
|
-
const checkGameEnd = useCallback(() => {
|
|
17868
|
-
const pa = units.filter((u) => u.team === "player" && u.health > 0);
|
|
17869
|
-
const ea = units.filter((u) => u.team === "enemy" && u.health > 0);
|
|
17870
|
-
if (pa.length === 0) {
|
|
17871
|
-
setGameResult("defeat");
|
|
17872
|
-
setCurrentPhase("game_over");
|
|
17873
|
-
onGameEnd?.("defeat");
|
|
17874
|
-
} else if (ea.length === 0) {
|
|
17875
|
-
setGameResult("victory");
|
|
17876
|
-
setCurrentPhase("game_over");
|
|
17877
|
-
onGameEnd?.("victory");
|
|
17878
|
-
}
|
|
17879
|
-
}, [units, onGameEnd]);
|
|
17880
|
-
const handleUnitClick = useCallback((unitId) => {
|
|
17881
|
-
const unit = units.find((u) => u.id === unitId);
|
|
17882
|
-
if (!unit) return;
|
|
17883
|
-
if (currentPhase === "observation" || currentPhase === "selection") {
|
|
17884
|
-
if (unit.team === "player") {
|
|
17885
|
-
setSelectedUnitId(unitId);
|
|
17886
|
-
setCurrentPhase("movement");
|
|
17887
|
-
}
|
|
17888
|
-
} else if (currentPhase === "action" && selectedUnit) {
|
|
17889
|
-
if (unit.team === "enemy" && attackTargets.some((t) => t.x === unit.position.x && t.y === unit.position.y)) {
|
|
17890
|
-
const damage = calculateDamage ? calculateDamage(selectedUnit, unit) : Math.max(1, selectedUnit.attack - unit.defense);
|
|
17891
|
-
const newHealth = Math.max(0, unit.health - damage);
|
|
17892
|
-
setUnits((prev) => prev.map((u) => u.id === unit.id ? { ...u, health: newHealth } : u));
|
|
17893
|
-
setIsShaking(true);
|
|
17894
|
-
setTimeout(() => setIsShaking(false), 300);
|
|
17895
|
-
onAttack?.(selectedUnit, unit, damage);
|
|
17896
|
-
setSelectedUnitId(null);
|
|
17897
|
-
setCurrentPhase("observation");
|
|
17898
|
-
setCurrentTurn((t) => t + 1);
|
|
17899
|
-
setTimeout(checkGameEnd, 100);
|
|
18831
|
+
className: "border-b-2 border-x-0 border-t-0 border-[var(--color-border)] flex items-center justify-between flex-shrink-0",
|
|
18832
|
+
children: [
|
|
18833
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
18834
|
+
/* @__PURE__ */ jsx(Typography, { variant: "h3", children: title }),
|
|
18835
|
+
subtitle && /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "secondary", className: "mt-1", children: subtitle })
|
|
18836
|
+
] }),
|
|
18837
|
+
headerActions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: headerActions })
|
|
18838
|
+
]
|
|
17900
18839
|
}
|
|
17901
|
-
|
|
17902
|
-
|
|
17903
|
-
|
|
17904
|
-
|
|
17905
|
-
|
|
17906
|
-
|
|
17907
|
-
|
|
17908
|
-
|
|
17909
|
-
|
|
17910
|
-
|
|
17911
|
-
(prev) => prev.map((u) => u.id === selectedUnitId ? { ...u, position: { x, y } } : u)
|
|
17912
|
-
);
|
|
17913
|
-
onUnitMove?.(selectedUnit, to);
|
|
17914
|
-
setCurrentPhase("action");
|
|
17915
|
-
});
|
|
18840
|
+
),
|
|
18841
|
+
/* @__PURE__ */ jsx(Box, { fullWidth: true, overflow: "auto", className: "flex-1", children: /* @__PURE__ */ jsx(Box, { padding: "lg", children }) }),
|
|
18842
|
+
footer && /* @__PURE__ */ jsx(
|
|
18843
|
+
Box,
|
|
18844
|
+
{
|
|
18845
|
+
padding: "md",
|
|
18846
|
+
border: true,
|
|
18847
|
+
bg: "muted",
|
|
18848
|
+
className: "border-t-2 border-x-0 border-b-0 border-[var(--color-border)] flex-shrink-0",
|
|
18849
|
+
children: footer
|
|
17916
18850
|
}
|
|
17917
|
-
|
|
17918
|
-
}
|
|
17919
|
-
|
|
17920
|
-
|
|
17921
|
-
|
|
17922
|
-
|
|
17923
|
-
|
|
17924
|
-
|
|
17925
|
-
|
|
17926
|
-
|
|
17927
|
-
|
|
17928
|
-
|
|
17929
|
-
|
|
17930
|
-
|
|
17931
|
-
|
|
17932
|
-
|
|
17933
|
-
|
|
17934
|
-
|
|
17935
|
-
|
|
17936
|
-
|
|
17937
|
-
|
|
17938
|
-
|
|
17939
|
-
|
|
17940
|
-
|
|
17941
|
-
|
|
17942
|
-
|
|
17943
|
-
|
|
17944
|
-
|
|
17945
|
-
|
|
17946
|
-
|
|
17947
|
-
|
|
17948
|
-
|
|
17949
|
-
|
|
17950
|
-
|
|
17951
|
-
|
|
17952
|
-
|
|
17953
|
-
|
|
17954
|
-
|
|
17955
|
-
|
|
17956
|
-
|
|
17957
|
-
|
|
17958
|
-
|
|
17959
|
-
|
|
17960
|
-
|
|
17961
|
-
|
|
17962
|
-
|
|
17963
|
-
|
|
17964
|
-
|
|
17965
|
-
|
|
17966
|
-
|
|
17967
|
-
|
|
17968
|
-
@keyframes battle-shake {
|
|
17969
|
-
0%, 100% { transform: translate(0, 0); }
|
|
17970
|
-
10% { transform: translate(-3px, -2px); }
|
|
17971
|
-
20% { transform: translate(3px, 1px); }
|
|
17972
|
-
30% { transform: translate(-2px, 3px); }
|
|
17973
|
-
40% { transform: translate(2px, -1px); }
|
|
17974
|
-
50% { transform: translate(-3px, 2px); }
|
|
17975
|
-
60% { transform: translate(3px, -2px); }
|
|
17976
|
-
70% { transform: translate(-1px, 3px); }
|
|
17977
|
-
80% { transform: translate(2px, -3px); }
|
|
17978
|
-
90% { transform: translate(-2px, 1px); }
|
|
18851
|
+
)
|
|
18852
|
+
] });
|
|
18853
|
+
};
|
|
18854
|
+
GenericAppTemplate.displayName = "GenericAppTemplate";
|
|
18855
|
+
var GameShell = ({
|
|
18856
|
+
appName = "Game",
|
|
18857
|
+
hud,
|
|
18858
|
+
className,
|
|
18859
|
+
showTopBar = true
|
|
18860
|
+
}) => {
|
|
18861
|
+
return /* @__PURE__ */ jsxs(
|
|
18862
|
+
"div",
|
|
18863
|
+
{
|
|
18864
|
+
className: cn(
|
|
18865
|
+
"game-shell",
|
|
18866
|
+
"flex flex-col w-full h-screen overflow-hidden",
|
|
18867
|
+
className
|
|
18868
|
+
),
|
|
18869
|
+
style: {
|
|
18870
|
+
width: "100vw",
|
|
18871
|
+
height: "100vh",
|
|
18872
|
+
display: "flex",
|
|
18873
|
+
flexDirection: "column",
|
|
18874
|
+
overflow: "hidden",
|
|
18875
|
+
background: "var(--color-background, #0a0a0f)",
|
|
18876
|
+
color: "var(--color-text, #e0e0e0)"
|
|
18877
|
+
},
|
|
18878
|
+
children: [
|
|
18879
|
+
showTopBar && /* @__PURE__ */ jsxs(
|
|
18880
|
+
"header",
|
|
18881
|
+
{
|
|
18882
|
+
className: "game-shell__header",
|
|
18883
|
+
style: {
|
|
18884
|
+
display: "flex",
|
|
18885
|
+
alignItems: "center",
|
|
18886
|
+
justifyContent: "space-between",
|
|
18887
|
+
padding: "0.5rem 1rem",
|
|
18888
|
+
borderBottom: "1px solid var(--color-border, #2a2a3a)",
|
|
18889
|
+
background: "var(--color-surface, #12121f)",
|
|
18890
|
+
flexShrink: 0
|
|
18891
|
+
},
|
|
18892
|
+
children: [
|
|
18893
|
+
/* @__PURE__ */ jsx(
|
|
18894
|
+
"span",
|
|
18895
|
+
{
|
|
18896
|
+
style: {
|
|
18897
|
+
fontWeight: 700,
|
|
18898
|
+
fontSize: "1.1rem",
|
|
18899
|
+
letterSpacing: "0.02em"
|
|
18900
|
+
},
|
|
18901
|
+
children: appName
|
|
17979
18902
|
}
|
|
17980
|
-
|
|
17981
|
-
|
|
17982
|
-
|
|
17983
|
-
|
|
18903
|
+
),
|
|
18904
|
+
hud && /* @__PURE__ */ jsx("div", { className: "game-shell__hud", children: hud })
|
|
18905
|
+
]
|
|
18906
|
+
}
|
|
18907
|
+
),
|
|
17984
18908
|
/* @__PURE__ */ jsx(
|
|
17985
|
-
|
|
18909
|
+
"main",
|
|
17986
18910
|
{
|
|
17987
|
-
|
|
17988
|
-
|
|
17989
|
-
|
|
17990
|
-
|
|
17991
|
-
|
|
17992
|
-
|
|
17993
|
-
|
|
17994
|
-
onTileClick: handleTileClick,
|
|
17995
|
-
onUnitClick: handleUnitClick,
|
|
17996
|
-
onTileHover: (x, y) => setHoveredTile({ x, y }),
|
|
17997
|
-
onTileLeave: () => setHoveredTile(null),
|
|
17998
|
-
scale,
|
|
17999
|
-
assetBaseUrl: assetManifest?.baseUrl,
|
|
18000
|
-
assetManifest,
|
|
18001
|
-
backgroundImage,
|
|
18002
|
-
onDrawEffects,
|
|
18003
|
-
hasActiveEffects: hasActiveEffects2,
|
|
18004
|
-
effectSpriteUrls,
|
|
18005
|
-
resolveUnitFrame,
|
|
18006
|
-
unitScale
|
|
18911
|
+
className: "game-shell__content",
|
|
18912
|
+
style: {
|
|
18913
|
+
flex: 1,
|
|
18914
|
+
overflow: "hidden",
|
|
18915
|
+
position: "relative"
|
|
18916
|
+
},
|
|
18917
|
+
children: /* @__PURE__ */ jsx(Outlet, {})
|
|
18007
18918
|
}
|
|
18008
|
-
)
|
|
18009
|
-
|
|
18010
|
-
|
|
18011
|
-
|
|
18012
|
-
|
|
18013
|
-
|
|
18014
|
-
|
|
18015
|
-
|
|
18016
|
-
|
|
18017
|
-
|
|
18018
|
-
|
|
18019
|
-
|
|
18020
|
-
|
|
18021
|
-
|
|
18022
|
-
|
|
18023
|
-
|
|
18024
|
-
|
|
18025
|
-
|
|
18026
|
-
|
|
18027
|
-
|
|
18028
|
-
|
|
18029
|
-
|
|
18030
|
-
|
|
18031
|
-
|
|
18032
|
-
|
|
18033
|
-
|
|
18034
|
-
|
|
18035
|
-
|
|
18036
|
-
|
|
18037
|
-
|
|
18038
|
-
|
|
18039
|
-
|
|
18040
|
-
}
|
|
18041
|
-
),
|
|
18042
|
-
/* @__PURE__ */ jsxs("p", { className: "text-gray-300", children: [
|
|
18043
|
-
"Turns played: ",
|
|
18044
|
-
currentTurn
|
|
18045
|
-
] }),
|
|
18046
|
-
/* @__PURE__ */ jsx(
|
|
18047
|
-
"button",
|
|
18048
|
-
{
|
|
18049
|
-
className: "px-8 py-3 rounded-lg bg-[var(--color-primary)] text-white font-semibold hover:opacity-90",
|
|
18050
|
-
onClick: handleReset,
|
|
18051
|
-
children: "Play Again"
|
|
18052
|
-
}
|
|
18053
|
-
)
|
|
18054
|
-
] }) }))
|
|
18055
|
-
] });
|
|
18056
|
-
}
|
|
18057
|
-
BattleTemplate.displayName = "BattleTemplate";
|
|
18058
|
-
function CastleTemplate({
|
|
18059
|
-
tiles,
|
|
18060
|
-
features = [],
|
|
18061
|
-
units = [],
|
|
18919
|
+
)
|
|
18920
|
+
]
|
|
18921
|
+
}
|
|
18922
|
+
);
|
|
18923
|
+
};
|
|
18924
|
+
GameShell.displayName = "GameShell";
|
|
18925
|
+
function BattleTemplate({
|
|
18926
|
+
entity,
|
|
18927
|
+
scale = 0.45,
|
|
18928
|
+
unitScale = 1,
|
|
18929
|
+
className
|
|
18930
|
+
}) {
|
|
18931
|
+
return /* @__PURE__ */ jsx(
|
|
18932
|
+
BattleBoard,
|
|
18933
|
+
{
|
|
18934
|
+
entity,
|
|
18935
|
+
scale,
|
|
18936
|
+
unitScale,
|
|
18937
|
+
tileClickEvent: "TILE_CLICK",
|
|
18938
|
+
unitClickEvent: "UNIT_CLICK",
|
|
18939
|
+
endTurnEvent: "END_TURN",
|
|
18940
|
+
cancelEvent: "CANCEL",
|
|
18941
|
+
gameEndEvent: "GAME_END",
|
|
18942
|
+
playAgainEvent: "PLAY_AGAIN",
|
|
18943
|
+
attackEvent: "ATTACK",
|
|
18944
|
+
className
|
|
18945
|
+
}
|
|
18946
|
+
);
|
|
18947
|
+
}
|
|
18948
|
+
BattleTemplate.displayName = "BattleTemplate";
|
|
18949
|
+
function CastleTemplate({
|
|
18950
|
+
entity,
|
|
18062
18951
|
scale = 0.45,
|
|
18063
|
-
assetManifest,
|
|
18064
|
-
backgroundImage,
|
|
18065
|
-
header,
|
|
18066
|
-
sidePanel,
|
|
18067
|
-
overlay,
|
|
18068
|
-
footer,
|
|
18069
|
-
onFeatureClick,
|
|
18070
|
-
onUnitClick,
|
|
18071
|
-
onTileClick,
|
|
18072
18952
|
className
|
|
18073
18953
|
}) {
|
|
18074
|
-
|
|
18075
|
-
|
|
18076
|
-
|
|
18077
|
-
|
|
18078
|
-
|
|
18079
|
-
|
|
18080
|
-
|
|
18081
|
-
|
|
18082
|
-
|
|
18083
|
-
(u) => u.position.x === hoveredTile.x && u.position.y === hoveredTile.y
|
|
18084
|
-
) ?? null;
|
|
18085
|
-
}, [hoveredTile, units]);
|
|
18086
|
-
const maxY = Math.max(...tiles.map((t) => t.y), 0);
|
|
18087
|
-
const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
|
|
18088
|
-
const tileToScreen = useCallback(
|
|
18089
|
-
(tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
|
|
18090
|
-
[scale, baseOffsetX]
|
|
18091
|
-
);
|
|
18092
|
-
const handleTileClick = useCallback((x, y) => {
|
|
18093
|
-
const feature = features.find((f) => f.x === x && f.y === y);
|
|
18094
|
-
if (feature) {
|
|
18095
|
-
setSelectedFeature(feature);
|
|
18096
|
-
onFeatureClick?.(feature);
|
|
18954
|
+
return /* @__PURE__ */ jsx(
|
|
18955
|
+
CastleBoard,
|
|
18956
|
+
{
|
|
18957
|
+
entity,
|
|
18958
|
+
scale,
|
|
18959
|
+
featureClickEvent: "FEATURE_CLICK",
|
|
18960
|
+
unitClickEvent: "UNIT_CLICK",
|
|
18961
|
+
tileClickEvent: "TILE_CLICK",
|
|
18962
|
+
className
|
|
18097
18963
|
}
|
|
18098
|
-
onTileClick?.(x, y);
|
|
18099
|
-
}, [features, onFeatureClick, onTileClick]);
|
|
18100
|
-
const handleUnitClick = useCallback((unitId) => {
|
|
18101
|
-
const unit = units.find((u) => u.id === unitId);
|
|
18102
|
-
if (unit) onUnitClick?.(unit);
|
|
18103
|
-
}, [units, onUnitClick]);
|
|
18104
|
-
const clearSelection = useCallback(() => setSelectedFeature(null), []);
|
|
18105
|
-
const ctx = useMemo(
|
|
18106
|
-
() => ({
|
|
18107
|
-
hoveredTile,
|
|
18108
|
-
hoveredFeature,
|
|
18109
|
-
hoveredUnit,
|
|
18110
|
-
selectedFeature,
|
|
18111
|
-
clearSelection,
|
|
18112
|
-
tileToScreen,
|
|
18113
|
-
scale
|
|
18114
|
-
}),
|
|
18115
|
-
[hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale]
|
|
18116
18964
|
);
|
|
18117
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("castle-template min-h-screen flex flex-col bg-[var(--color-background)]", className), children: [
|
|
18118
|
-
header && header(ctx),
|
|
18119
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
|
|
18120
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-auto p-4 relative", children: [
|
|
18121
|
-
/* @__PURE__ */ jsx(
|
|
18122
|
-
IsometricCanvas_default,
|
|
18123
|
-
{
|
|
18124
|
-
tiles,
|
|
18125
|
-
units,
|
|
18126
|
-
features,
|
|
18127
|
-
hoveredTile,
|
|
18128
|
-
onTileClick: handleTileClick,
|
|
18129
|
-
onUnitClick: handleUnitClick,
|
|
18130
|
-
onTileHover: (x, y) => setHoveredTile({ x, y }),
|
|
18131
|
-
onTileLeave: () => setHoveredTile(null),
|
|
18132
|
-
scale,
|
|
18133
|
-
assetBaseUrl: assetManifest?.baseUrl,
|
|
18134
|
-
assetManifest,
|
|
18135
|
-
backgroundImage
|
|
18136
|
-
}
|
|
18137
|
-
),
|
|
18138
|
-
overlay && overlay(ctx)
|
|
18139
|
-
] }),
|
|
18140
|
-
sidePanel && /* @__PURE__ */ jsx("div", { className: "w-96 shrink-0 border-l border-[var(--color-border)] bg-[var(--color-surface)] overflow-y-auto", children: sidePanel(ctx) })
|
|
18141
|
-
] }),
|
|
18142
|
-
footer && footer(ctx)
|
|
18143
|
-
] });
|
|
18144
18965
|
}
|
|
18145
18966
|
CastleTemplate.displayName = "CastleTemplate";
|
|
18146
|
-
function defaultIsInRange(from, to, range) {
|
|
18147
|
-
return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
|
|
18148
|
-
}
|
|
18149
18967
|
function WorldMapTemplate({
|
|
18150
|
-
|
|
18151
|
-
heroes,
|
|
18152
|
-
features = [],
|
|
18153
|
-
selectedHeroId,
|
|
18968
|
+
entity,
|
|
18154
18969
|
scale = 0.4,
|
|
18155
18970
|
unitScale = 2.5,
|
|
18156
|
-
assetManifest,
|
|
18157
|
-
backgroundImage,
|
|
18158
18971
|
allowMoveAllHeroes = false,
|
|
18159
|
-
isInRange = defaultIsInRange,
|
|
18160
|
-
header,
|
|
18161
|
-
sidePanel,
|
|
18162
|
-
overlay,
|
|
18163
|
-
footer,
|
|
18164
|
-
onHeroSelect,
|
|
18165
|
-
onHeroMove,
|
|
18166
|
-
onBattleEncounter,
|
|
18167
|
-
onFeatureEnter,
|
|
18168
|
-
effectSpriteUrls = [],
|
|
18169
|
-
resolveUnitFrame,
|
|
18170
18972
|
className
|
|
18171
18973
|
}) {
|
|
18172
|
-
|
|
18173
|
-
|
|
18174
|
-
|
|
18175
|
-
|
|
18176
|
-
|
|
18177
|
-
|
|
18178
|
-
|
|
18179
|
-
|
|
18180
|
-
|
|
18181
|
-
|
|
18182
|
-
|
|
18183
|
-
|
|
18184
|
-
[hexes]
|
|
18185
|
-
);
|
|
18186
|
-
const baseUnits = useMemo(
|
|
18187
|
-
() => heroes.map((hero) => ({
|
|
18188
|
-
id: hero.id,
|
|
18189
|
-
position: hero.position,
|
|
18190
|
-
name: hero.name,
|
|
18191
|
-
team: hero.owner === "enemy" ? "enemy" : "player",
|
|
18192
|
-
health: 100,
|
|
18193
|
-
maxHealth: 100,
|
|
18194
|
-
sprite: hero.sprite
|
|
18195
|
-
})),
|
|
18196
|
-
[heroes]
|
|
18197
|
-
);
|
|
18198
|
-
const MOVE_SPEED_MS_PER_TILE = 300;
|
|
18199
|
-
const movementAnimRef = useRef(null);
|
|
18200
|
-
const [movingPositions, setMovingPositions] = useState(/* @__PURE__ */ new Map());
|
|
18201
|
-
const startMoveAnimation = useCallback((heroId, from, to, onComplete) => {
|
|
18202
|
-
const dist = Math.max(Math.abs(to.x - from.x), Math.abs(to.y - from.y));
|
|
18203
|
-
movementAnimRef.current = { heroId, from, to, elapsed: 0, duration: dist * MOVE_SPEED_MS_PER_TILE, onComplete };
|
|
18204
|
-
}, []);
|
|
18205
|
-
useEffect(() => {
|
|
18206
|
-
const interval = setInterval(() => {
|
|
18207
|
-
const anim2 = movementAnimRef.current;
|
|
18208
|
-
if (!anim2) return;
|
|
18209
|
-
anim2.elapsed += 16;
|
|
18210
|
-
const t = Math.min(anim2.elapsed / anim2.duration, 1);
|
|
18211
|
-
const eased = 1 - (1 - t) * (1 - t);
|
|
18212
|
-
const cx = anim2.from.x + (anim2.to.x - anim2.from.x) * eased;
|
|
18213
|
-
const cy = anim2.from.y + (anim2.to.y - anim2.from.y) * eased;
|
|
18214
|
-
if (t >= 1) {
|
|
18215
|
-
movementAnimRef.current = null;
|
|
18216
|
-
setMovingPositions((prev) => {
|
|
18217
|
-
const n = new Map(prev);
|
|
18218
|
-
n.delete(anim2.heroId);
|
|
18219
|
-
return n;
|
|
18220
|
-
});
|
|
18221
|
-
anim2.onComplete();
|
|
18222
|
-
} else {
|
|
18223
|
-
setMovingPositions((prev) => {
|
|
18224
|
-
const n = new Map(prev);
|
|
18225
|
-
n.set(anim2.heroId, { x: cx, y: cy });
|
|
18226
|
-
return n;
|
|
18227
|
-
});
|
|
18228
|
-
}
|
|
18229
|
-
}, 16);
|
|
18230
|
-
return () => clearInterval(interval);
|
|
18231
|
-
}, []);
|
|
18232
|
-
const isoUnits = useMemo(() => {
|
|
18233
|
-
if (movingPositions.size === 0) return baseUnits;
|
|
18234
|
-
return baseUnits.map((u) => {
|
|
18235
|
-
const pos = movingPositions.get(u.id);
|
|
18236
|
-
return pos ? { ...u, position: pos } : u;
|
|
18237
|
-
});
|
|
18238
|
-
}, [baseUnits, movingPositions]);
|
|
18239
|
-
const validMoves = useMemo(() => {
|
|
18240
|
-
if (!selectedHero || selectedHero.movement <= 0) return [];
|
|
18241
|
-
const moves = [];
|
|
18242
|
-
hexes.forEach((hex) => {
|
|
18243
|
-
if (hex.passable === false) return;
|
|
18244
|
-
if (hex.x === selectedHero.position.x && hex.y === selectedHero.position.y) return;
|
|
18245
|
-
if (!isInRange(selectedHero.position, { x: hex.x, y: hex.y }, selectedHero.movement)) return;
|
|
18246
|
-
if (heroes.some((h) => h.position.x === hex.x && h.position.y === hex.y && h.owner === selectedHero.owner)) return;
|
|
18247
|
-
moves.push({ x: hex.x, y: hex.y });
|
|
18248
|
-
});
|
|
18249
|
-
return moves;
|
|
18250
|
-
}, [selectedHero, hexes, heroes, isInRange]);
|
|
18251
|
-
const attackTargets = useMemo(() => {
|
|
18252
|
-
if (!selectedHero || selectedHero.movement <= 0) return [];
|
|
18253
|
-
return heroes.filter((h) => h.owner !== selectedHero.owner).filter((h) => isInRange(selectedHero.position, h.position, selectedHero.movement)).map((h) => h.position);
|
|
18254
|
-
}, [selectedHero, heroes, isInRange]);
|
|
18255
|
-
const maxY = Math.max(...hexes.map((h) => h.y), 0);
|
|
18256
|
-
const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
|
|
18257
|
-
const tileToScreen = useCallback(
|
|
18258
|
-
(tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
|
|
18259
|
-
[scale, baseOffsetX]
|
|
18260
|
-
);
|
|
18261
|
-
const hoveredHex = useMemo(
|
|
18262
|
-
() => hoveredTile ? hexes.find((h) => h.x === hoveredTile.x && h.y === hoveredTile.y) ?? null : null,
|
|
18263
|
-
[hoveredTile, hexes]
|
|
18264
|
-
);
|
|
18265
|
-
const hoveredHero = useMemo(
|
|
18266
|
-
() => hoveredTile ? heroes.find((h) => h.position.x === hoveredTile.x && h.position.y === hoveredTile.y) ?? null : null,
|
|
18267
|
-
[hoveredTile, heroes]
|
|
18268
|
-
);
|
|
18269
|
-
const handleTileClick = useCallback((x, y) => {
|
|
18270
|
-
if (movementAnimRef.current) return;
|
|
18271
|
-
const hex = hexes.find((h) => h.x === x && h.y === y);
|
|
18272
|
-
if (!hex) return;
|
|
18273
|
-
if (selectedHero && validMoves.some((m) => m.x === x && m.y === y)) {
|
|
18274
|
-
startMoveAnimation(selectedHero.id, { ...selectedHero.position }, { x, y }, () => {
|
|
18275
|
-
onHeroMove?.(selectedHero.id, x, y);
|
|
18276
|
-
if (hex.feature && hex.feature !== "none") {
|
|
18277
|
-
onFeatureEnter?.(selectedHero.id, hex);
|
|
18278
|
-
}
|
|
18279
|
-
});
|
|
18280
|
-
return;
|
|
18281
|
-
}
|
|
18282
|
-
const enemy = heroes.find((h) => h.position.x === x && h.position.y === y && h.owner === "enemy");
|
|
18283
|
-
if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
|
|
18284
|
-
onBattleEncounter?.(selectedHero.id, enemy.id);
|
|
18285
|
-
}
|
|
18286
|
-
}, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter]);
|
|
18287
|
-
const handleUnitClick = useCallback((unitId) => {
|
|
18288
|
-
const hero = heroes.find((h) => h.id === unitId);
|
|
18289
|
-
if (hero && (hero.owner === "player" || allowMoveAllHeroes)) {
|
|
18290
|
-
onHeroSelect?.(unitId);
|
|
18974
|
+
return /* @__PURE__ */ jsx(
|
|
18975
|
+
WorldMapBoard,
|
|
18976
|
+
{
|
|
18977
|
+
entity,
|
|
18978
|
+
scale,
|
|
18979
|
+
unitScale,
|
|
18980
|
+
allowMoveAllHeroes,
|
|
18981
|
+
heroSelectEvent: "HERO_SELECT",
|
|
18982
|
+
heroMoveEvent: "HERO_MOVE",
|
|
18983
|
+
battleEncounterEvent: "BATTLE_ENCOUNTER",
|
|
18984
|
+
featureEnterEvent: "FEATURE_ENTER",
|
|
18985
|
+
className
|
|
18291
18986
|
}
|
|
18292
|
-
}, [heroes, onHeroSelect, allowMoveAllHeroes]);
|
|
18293
|
-
const selectHero = useCallback((id) => onHeroSelect?.(id), [onHeroSelect]);
|
|
18294
|
-
const ctx = useMemo(
|
|
18295
|
-
() => ({
|
|
18296
|
-
hoveredTile,
|
|
18297
|
-
hoveredHex,
|
|
18298
|
-
hoveredHero,
|
|
18299
|
-
selectedHero,
|
|
18300
|
-
validMoves,
|
|
18301
|
-
selectHero,
|
|
18302
|
-
tileToScreen,
|
|
18303
|
-
scale
|
|
18304
|
-
}),
|
|
18305
|
-
[hoveredTile, hoveredHex, hoveredHero, selectedHero, validMoves, selectHero, tileToScreen, scale]
|
|
18306
18987
|
);
|
|
18307
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("world-map-template min-h-screen flex flex-col bg-[var(--color-background)]", className), children: [
|
|
18308
|
-
header && header(ctx),
|
|
18309
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
|
|
18310
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-auto p-4 relative", children: [
|
|
18311
|
-
/* @__PURE__ */ jsx(
|
|
18312
|
-
IsometricCanvas_default,
|
|
18313
|
-
{
|
|
18314
|
-
tiles,
|
|
18315
|
-
units: isoUnits,
|
|
18316
|
-
features,
|
|
18317
|
-
selectedUnitId: selectedHeroId,
|
|
18318
|
-
validMoves,
|
|
18319
|
-
attackTargets,
|
|
18320
|
-
hoveredTile,
|
|
18321
|
-
onTileClick: handleTileClick,
|
|
18322
|
-
onUnitClick: handleUnitClick,
|
|
18323
|
-
onTileHover: (x, y) => setHoveredTile({ x, y }),
|
|
18324
|
-
onTileLeave: () => setHoveredTile(null),
|
|
18325
|
-
scale,
|
|
18326
|
-
assetBaseUrl: assetManifest?.baseUrl,
|
|
18327
|
-
assetManifest,
|
|
18328
|
-
backgroundImage,
|
|
18329
|
-
effectSpriteUrls,
|
|
18330
|
-
resolveUnitFrame,
|
|
18331
|
-
unitScale
|
|
18332
|
-
}
|
|
18333
|
-
),
|
|
18334
|
-
overlay && overlay(ctx)
|
|
18335
|
-
] }),
|
|
18336
|
-
sidePanel && /* @__PURE__ */ jsx("div", { className: "w-80 shrink-0 border-l border-[var(--color-border)] bg-[var(--color-surface)] overflow-y-auto p-4", children: sidePanel(ctx) })
|
|
18337
|
-
] }),
|
|
18338
|
-
footer && footer(ctx)
|
|
18339
|
-
] });
|
|
18340
18988
|
}
|
|
18341
18989
|
WorldMapTemplate.displayName = "WorldMapTemplate";
|
|
18342
18990
|
|
|
18343
|
-
export { Accordion, Card2 as ActionCard, Alert, AuthLayout, Avatar, Badge, BattleTemplate, Box, Breadcrumb, Button, ButtonGroup, CanvasEffect, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, CastleTemplate, Center, Chart, Checkbox, CodeViewer, ConditionalWrapper, ConfirmDialog, Container, ControlButton, CounterTemplate, CrudTemplate, DashboardGrid, DashboardLayout, DataTable, DetailPanel, DialogueBox, Divider, DocumentViewer, Drawer, DrawerSlot, EmptyState, ErrorState, FEATURE_COLORS, FLOOR_HEIGHT, FilterGroup, Flex, FloatingActionButton, Form, FormActions, FormField, FormLayout, FormSection, FormSectionHeader, FormTemplate, GameHud, GameMenu, GameOverScreen, GameShell, GameTemplate, GenericAppTemplate, GraphCanvas, Grid2 as Grid, HStack, Header, Heading, HealthBar, Icon, Input, InputGroup, InventoryPanel, IsometricCanvas, Label, LawReferenceTooltip, List2 as List, ListTemplate, LoadingState, MasterDetail, MediaGallery, Menu2 as Menu, Meter, Modal, ModalSlot, Navigation, OrbitalVisualization, Overlay, PageHeader, Pagination, Popover, ProgressBar, Radio, RelationSelect, RepeatableFormSection, SHEET_COLUMNS, SPRITE_SHEET_LAYOUT, ScoreDisplay, SearchInput, Section, Select, SettingsTemplate, SidePanel, Sidebar, SignaturePad, SimpleGrid, SlotContentRenderer, Spacer, Spinner, Split, SplitPane, Sprite, Stack, StatCard, Switch, TILE_HEIGHT, TILE_WIDTH, TabbedContainer, Table, Tabs, Text, TextHighlight, Textarea, ThemeSelector, ThemeToggle, Timeline, Toast, ToastSlot, Tooltip, Typography, UISlotComponent, UISlotRenderer, VStack, ViolationAlert, WizardContainer, WizardNavigation, WizardProgress, WorldMapTemplate, createUnitAnimationState, drawSprite, getCurrentFrame, inferDirection, isoToScreen, resolveFrame, resolveSheetDirection, screenToIso, tickAnimationState, transitionAnimation, useCamera, useImageCache, useSpriteAnimations };
|
|
18991
|
+
export { Accordion, Card2 as ActionCard, Alert, AuthLayout, Avatar, Badge, BattleBoard, BattleTemplate, Box, Breadcrumb, Button, ButtonGroup, CanvasEffect, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, CastleBoard, CastleTemplate, Center, Chart, Checkbox, CodeViewer, CollapsibleSection, ConditionalWrapper, ConfirmDialog, Container, ControlButton, CounterTemplate, CrudTemplate, DIAMOND_TOP_Y, DashboardGrid, DashboardLayout, DataTable, DetailPanel, DialogueBox, Divider, DocumentViewer, Drawer, DrawerSlot, EditorCheckbox, EditorSelect, EditorSlider, EditorTextInput, EditorToolbar, EmptyState, ErrorState, FEATURE_COLORS, FEATURE_TYPES, FLOOR_HEIGHT, FilterGroup, Flex, FloatingActionButton, Form, FormActions, FormField, FormLayout, FormSection, FormSectionHeader, FormTemplate, GameHud, GameMenu, GameOverScreen, GameShell, GameTemplate, GenericAppTemplate, GraphCanvas, Grid2 as Grid, HStack, Header, Heading, HealthBar, Icon, Input, InputGroup, InventoryPanel, IsometricCanvas, Label, LawReferenceTooltip, List2 as List, ListTemplate, LoadingState, MasterDetail, MediaGallery, Menu2 as Menu, Meter, Modal, ModalSlot, Navigation, OrbitalVisualization, Overlay, PageHeader, Pagination, PhysicsManager, Popover, ProgressBar, Radio, RelationSelect, RepeatableFormSection, SHEET_COLUMNS, SPRITE_SHEET_LAYOUT, ScoreDisplay, SearchInput, Section, Select, SettingsTemplate, SidePanel, Sidebar, SignaturePad, SimpleGrid, SlotContentRenderer, Spacer, Spinner, Split, SplitPane, Sprite, Stack, StatCard, StatusBar, Switch, TERRAIN_COLORS, TILE_HEIGHT, TILE_WIDTH, TabbedContainer, Table, Tabs, TerrainPalette, Text, TextHighlight, Textarea, ThemeSelector, ThemeToggle, Timeline, Toast, ToastSlot, Tooltip, Typography, UISlotComponent, UISlotRenderer, VStack, ViolationAlert, WizardContainer, WizardNavigation, WizardProgress, WorldMapBoard, WorldMapTemplate, createUnitAnimationState, drawSprite, getCurrentFrame, inferDirection, isoToScreen, resolveFrame, resolveSheetDirection, screenToIso, tickAnimationState, transitionAnimation, useCamera, useImageCache, usePhysics2D, useSpriteAnimations };
|