@almadar/ui 2.6.0 → 2.8.1
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-45CTDYBT.js → chunk-A5J5CNCU.js} +137 -1
- package/dist/{chunk-LEWQP2UP.js → chunk-NES4SBB7.js} +7896 -4304
- package/dist/components/index.d.ts +2018 -1416
- package/dist/components/index.js +130 -2053
- package/dist/lib/index.d.ts +33 -2
- package/dist/lib/index.js +1 -2
- package/dist/providers/index.js +2 -3
- package/package.json +1 -1
- package/dist/chunk-KKCVDUK7.js +0 -104
package/dist/components/index.js
CHANGED
|
@@ -2,23 +2,22 @@ import { useAuthContext } from '../chunk-2QM732NQ.js';
|
|
|
2
2
|
export { ENTITY_EVENTS, 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, usePinchZoom, usePlayer, usePreview, useResolvedEntity, useSelectedEntity, useSendOrbitalEvent, useSingletonEntity, useUIEvents, useUpdateEntity, useValidation } from '../chunk-2QM732NQ.js';
|
|
3
3
|
import { DEFAULT_CONFIG, renderStateMachineToDomData, parseContentSegments } from '../chunk-N6DJVKZ6.js';
|
|
4
4
|
import '../chunk-3HJHHULT.js';
|
|
5
|
-
import { VStack, HStack, Typography, Button, Icon, Box, Card, Avatar, Badge, SearchInput, Checkbox, Menu as Menu$1, Pagination, LoadingState, EmptyState, Modal, ErrorState, QuizBlock, CodeBlock, ScaledDiagram, MarkdownContent, Divider, ProgressBar,
|
|
6
|
-
export { Accordion, ActionButtons, Card2 as ActionCard, Alert, AnimatedCounter, Avatar, Badge, Box, Breadcrumb, Button, ButtonGroup, CalendarGrid, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, Center, ChartLegend, Checkbox, CodeBlock, ConditionalWrapper, ConfettiEffect, Container, ControlButton, DPad, DataGrid, DataList, DataTable, DateRangeSelector, DayCell, DetailPanel, Divider, Drawer, EmptyState, EntityDisplayEvents, ErrorBoundary, ErrorState, FilterGroup, Flex, FlipCard, FlipContainer, FloatingActionButton, Form, FormField, FormSectionHeader, GraphView, Grid, HStack, Heading, HealthBar, Icon, InfiniteScrollSentinel, Input, InputGroup, Label, LawReferenceTooltip, Lightbox, LineChart, LoadingState, MapView, MarkdownContent, MasterDetail, Menu, Modal, NumberStepper, Overlay, PageHeader, Pagination, Popover, ProgressBar, ProgressDots, PullToRefresh, QuizBlock, Radio, RangeSlider, RelationSelect, RepeatableFormSection, ScaledDiagram, ScoreDisplay, SearchInput, Select, SidePanel, SimpleGrid, Skeleton, SlotContentRenderer, SortableList, Spacer, Spinner, Sprite, Stack, StarRating, StatBadge, StatCard, StateIndicator, StatusDot, SwipeableRow, Switch, Tabs, Text, TextHighlight, Textarea, ThemeSelector, ThemeToggle, TimeSlotCell, Toast, Tooltip, TrendIndicator, TypewriterText, Typography, UISlotComponent, UISlotRenderer, UploadDropZone, VStack, ViolationAlert, WizardNavigation, WizardProgress, drawSprite } from '../chunk-
|
|
5
|
+
import { VStack, HStack, Typography, Button, Icon, Box, Card, Avatar, Badge, SearchInput, Checkbox, Menu as Menu$1, Pagination, LoadingState, EmptyState, Modal, ErrorState, QuizBlock, CodeBlock, ScaledDiagram, MarkdownContent, Divider, ProgressBar, isoToScreen, IsometricCanvas_default, Stack, Select, Drawer, Toast, Tabs, Input, ThemeToggle, TILE_WIDTH, EntityDisplayEvents, StateIndicator, Container } from '../chunk-NES4SBB7.js';
|
|
6
|
+
export { Accordion, ActionButton, ActionButtons, Card2 as ActionCard, Alert, AnimatedCounter, Avatar, Badge, Box, Breadcrumb, Button, ButtonGroup, CalendarGrid, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, Center, ChartLegend, Checkbox, ChoiceButton, CodeBlock, CombatLog, ComboCounter, ConditionalWrapper, ConfettiEffect, Container, ControlButton, CraftingRecipe, DIAMOND_TOP_Y, DPad, DamageNumber, DataGrid, DataList, DataTable, DateRangeSelector, DayCell, DetailPanel, DialogueBox, DialogueBubble, Divider, Drawer, EmptyState, EnemyPlate, EntityDisplayEvents, ErrorBoundary, ErrorState, FEATURE_COLORS, FLOOR_HEIGHT, FilterGroup, Flex, FlipCard, FlipContainer, FloatingActionButton, Form, FormField, FormSectionHeader, GameCanvas2D, GameHud, GameMenu, GameOverScreen, GraphView, Grid, HStack, Heading, HealthBar, HealthPanel, Icon, InfiniteScrollSentinel, Input, InputGroup, InventoryGrid, InventoryPanel, IsometricCanvas, ItemSlot, Label, LawReferenceTooltip, Lightbox, LineChart, LoadingState, MapView, MarkdownContent, MasterDetail, Menu, Meter, MiniMap, Modal, NumberStepper, Overlay, PageHeader, Pagination, PlatformerCanvas, Popover, PowerupSlots, ProgressBar, ProgressDots, PullToRefresh, QuestTracker, QuizBlock, Radio, RangeSlider, RelationSelect, RepeatableFormSection, ResourceBar, ResourceCounter, ScaledDiagram, ScoreBoard, ScoreDisplay, SearchInput, Select, SidePanel, SimpleGrid, Skeleton, SlotContentRenderer, SortableList, Spacer, Spinner, Sprite, Stack, StarRating, StatBadge, StatCard, StatDisplay, StateIndicator, StatusDot, StatusEffect, SwipeableRow, Switch, TILE_HEIGHT, TILE_WIDTH, Tabs, Text, TextHighlight, Textarea, ThemeSelector, ThemeToggle, TimeSlotCell, TimerDisplay, Toast, Tooltip, TrendIndicator, TurnIndicator, TurnPanel, TypewriterText, Typography, UISlotComponent, UISlotRenderer, UnitCommandBar, UploadDropZone, VStack, ViolationAlert, WaypointMarker, WizardNavigation, WizardProgress, XPBar, drawSprite, isoToScreen, screenToIso, useCamera, useImageCache } from '../chunk-NES4SBB7.js';
|
|
7
7
|
import '../chunk-DKQN5FVU.js';
|
|
8
8
|
import { useTranslate } from '../chunk-WGJIL4YR.js';
|
|
9
9
|
export { EntityDataProvider, I18nProvider, createTranslate, entityDataKeys, parseQueryBinding, useDragReorder, useEntity, useEntityDataAdapter, useEntityDetail, useEntityList, useEntityListSuspense, useEntitySuspense, useInfiniteScroll, useLongPress, usePullToRefresh, useQuerySingleton, useSwipeGesture, useTranslate } from '../chunk-WGJIL4YR.js';
|
|
10
10
|
import { useEventBus, useEventListener } from '../chunk-YXZM3WCF.js';
|
|
11
11
|
export { useEmitEvent, useEventBus, useEventListener } from '../chunk-YXZM3WCF.js';
|
|
12
12
|
export { DEFAULT_SLOTS, useUISlotManager } from '../chunk-3JGAROCW.js';
|
|
13
|
-
import { cn, getNestedValue } from '../chunk-
|
|
14
|
-
export { cn } from '../chunk-
|
|
13
|
+
import { cn, getNestedValue, bindCanvasCapture } from '../chunk-A5J5CNCU.js';
|
|
14
|
+
export { cn } from '../chunk-A5J5CNCU.js';
|
|
15
15
|
import '../chunk-TSETXL2E.js';
|
|
16
16
|
import '../chunk-K2D5D3WK.js';
|
|
17
17
|
export { clearEntities, getAllEntities, getByType, getEntity, getSingleton, removeEntity, spawnEntity, updateEntity, updateSingleton } from '../chunk-N7MVUW4R.js';
|
|
18
18
|
import { __publicField } from '../chunk-PKBMQBKP.js';
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
import { ChevronDown, X, Menu, ChevronRight, ChevronLeft, ArrowUp, ArrowDown, MoreVertical, Package, Check, AlertTriangle, Trash2, List as List$1, Printer, CheckCircle, XCircle, Play, RotateCcw, Send, Wrench, Bug, ArrowRight, Pause, SkipForward, Zap, Sword, Move, Heart, Shield, AlertCircle, Circle, Clock, CheckCircle2, Image as Image$1, Upload, ZoomIn, Eraser, FileText, ZoomOut, Download, Code, WrapText, Copy, Settings, Search, Bell, LogOut, Calendar, Pencil, Eye, MoreHorizontal, Minus, Plus } from 'lucide-react';
|
|
19
|
+
import React, { createContext, useState, useCallback, useMemo, useEffect, useRef, useContext } from 'react';
|
|
20
|
+
import { ChevronDown, X, Menu, ChevronRight, ChevronLeft, ArrowUp, ArrowDown, MoreVertical, Package, Check, AlertTriangle, Trash2, List as List$1, Printer, CheckCircle, XCircle, Play, RotateCcw, Send, Wrench, Bug, ArrowRight, Pause, SkipForward, AlertCircle, Circle, Clock, CheckCircle2, Image as Image$1, Upload, ZoomIn, Eraser, FileText, ZoomOut, Download, Code, WrapText, Copy, Settings, Search, Bell, LogOut, Calendar, Pencil, Eye, MoreHorizontal, Minus, Plus } from 'lucide-react';
|
|
22
21
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
23
22
|
import { createPortal } from 'react-dom';
|
|
24
23
|
import { useLocation, Link, Outlet } from 'react-router-dom';
|
|
@@ -33,7 +32,7 @@ var FormSection = ({
|
|
|
33
32
|
columns = 1,
|
|
34
33
|
className
|
|
35
34
|
}) => {
|
|
36
|
-
const [collapsed, setCollapsed] =
|
|
35
|
+
const [collapsed, setCollapsed] = React.useState(defaultCollapsed);
|
|
37
36
|
const { t } = useTranslate();
|
|
38
37
|
const eventBus = useEventBus();
|
|
39
38
|
const gridClass = {
|
|
@@ -41,7 +40,7 @@ var FormSection = ({
|
|
|
41
40
|
2: "grid-cols-1 md:grid-cols-2",
|
|
42
41
|
3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
|
|
43
42
|
}[columns];
|
|
44
|
-
|
|
43
|
+
React.useCallback(() => {
|
|
45
44
|
if (collapsible) {
|
|
46
45
|
setCollapsed((prev) => !prev);
|
|
47
46
|
eventBus.emit("UI:TOGGLE_COLLAPSE", { collapsed: !collapsed });
|
|
@@ -1013,7 +1012,7 @@ var List = ({
|
|
|
1013
1012
|
if (entity && typeof entity === "object" && "id" in entity) return [entity];
|
|
1014
1013
|
return [];
|
|
1015
1014
|
}, [entity]);
|
|
1016
|
-
const getItemActions =
|
|
1015
|
+
const getItemActions = React.useCallback(
|
|
1017
1016
|
(item) => {
|
|
1018
1017
|
if (!itemActions) return [];
|
|
1019
1018
|
if (typeof itemActions === "function") {
|
|
@@ -1518,7 +1517,7 @@ var WizardContainer = ({
|
|
|
1518
1517
|
const isCompleted = index < currentStep;
|
|
1519
1518
|
const stepKey = step.id ?? step.tabId ?? `step-${index}`;
|
|
1520
1519
|
const stepTitle = step.title ?? step.name ?? `Step ${index + 1}`;
|
|
1521
|
-
return /* @__PURE__ */ jsxs(
|
|
1520
|
+
return /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
1522
1521
|
/* @__PURE__ */ jsx(
|
|
1523
1522
|
Button,
|
|
1524
1523
|
{
|
|
@@ -2742,7 +2741,7 @@ var StateMachineView = ({
|
|
|
2742
2741
|
style: { top: title ? 30 : 0 },
|
|
2743
2742
|
children: [
|
|
2744
2743
|
entity && /* @__PURE__ */ jsx(EntityBox, { entity, config }),
|
|
2745
|
-
states.map((state) => renderStateNode ? /* @__PURE__ */ jsx(
|
|
2744
|
+
states.map((state) => renderStateNode ? /* @__PURE__ */ jsx(React.Fragment, { children: renderStateNode(state, config) }, state.id) : /* @__PURE__ */ jsx(
|
|
2746
2745
|
StateNode,
|
|
2747
2746
|
{
|
|
2748
2747
|
state,
|
|
@@ -3746,1048 +3745,6 @@ var TabbedContainer = ({
|
|
|
3746
3745
|
);
|
|
3747
3746
|
};
|
|
3748
3747
|
TabbedContainer.displayName = "TabbedContainer";
|
|
3749
|
-
function useImageCache(urls) {
|
|
3750
|
-
const cacheRef = useRef(/* @__PURE__ */ new Map());
|
|
3751
|
-
const loadingRef = useRef(/* @__PURE__ */ new Set());
|
|
3752
|
-
const [pendingCount, setPendingCount] = useState(0);
|
|
3753
|
-
useEffect(() => {
|
|
3754
|
-
const cache = cacheRef.current;
|
|
3755
|
-
const loading = loadingRef.current;
|
|
3756
|
-
let cancelled = false;
|
|
3757
|
-
const newUrls = urls.filter((url) => url && !cache.has(url) && !loading.has(url));
|
|
3758
|
-
if (newUrls.length === 0) return;
|
|
3759
|
-
setPendingCount((prev) => prev + newUrls.length);
|
|
3760
|
-
for (const url of newUrls) {
|
|
3761
|
-
loading.add(url);
|
|
3762
|
-
const img = new Image();
|
|
3763
|
-
img.crossOrigin = "anonymous";
|
|
3764
|
-
img.onload = () => {
|
|
3765
|
-
if (cancelled) return;
|
|
3766
|
-
cache.set(url, img);
|
|
3767
|
-
loading.delete(url);
|
|
3768
|
-
setPendingCount((prev) => Math.max(0, prev - 1));
|
|
3769
|
-
};
|
|
3770
|
-
img.onerror = () => {
|
|
3771
|
-
if (cancelled) return;
|
|
3772
|
-
loading.delete(url);
|
|
3773
|
-
setPendingCount((prev) => Math.max(0, prev - 1));
|
|
3774
|
-
};
|
|
3775
|
-
img.src = url;
|
|
3776
|
-
}
|
|
3777
|
-
return () => {
|
|
3778
|
-
cancelled = true;
|
|
3779
|
-
};
|
|
3780
|
-
}, [urls.join(",")]);
|
|
3781
|
-
const getImage = useCallback((url) => {
|
|
3782
|
-
return cacheRef.current.get(url);
|
|
3783
|
-
}, []);
|
|
3784
|
-
return {
|
|
3785
|
-
getImage,
|
|
3786
|
-
isLoaded: pendingCount === 0,
|
|
3787
|
-
pendingCount
|
|
3788
|
-
};
|
|
3789
|
-
}
|
|
3790
|
-
function useCamera() {
|
|
3791
|
-
const cameraRef = useRef({ x: 0, y: 0, zoom: 1 });
|
|
3792
|
-
const targetCameraRef = useRef(null);
|
|
3793
|
-
const isDraggingRef = useRef(false);
|
|
3794
|
-
const dragDistanceRef = useRef(0);
|
|
3795
|
-
const lastMouseRef = useRef({ x: 0, y: 0 });
|
|
3796
|
-
const handleMouseDown = useCallback((e) => {
|
|
3797
|
-
isDraggingRef.current = true;
|
|
3798
|
-
dragDistanceRef.current = 0;
|
|
3799
|
-
lastMouseRef.current = { x: e.clientX, y: e.clientY };
|
|
3800
|
-
if (e.button === 1 || e.button === 2) {
|
|
3801
|
-
e.preventDefault();
|
|
3802
|
-
}
|
|
3803
|
-
}, []);
|
|
3804
|
-
const handleMouseUp = useCallback(() => {
|
|
3805
|
-
isDraggingRef.current = false;
|
|
3806
|
-
}, []);
|
|
3807
|
-
const handleMouseMove = useCallback((e, drawFn) => {
|
|
3808
|
-
if (!isDraggingRef.current) return false;
|
|
3809
|
-
const dx = e.clientX - lastMouseRef.current.x;
|
|
3810
|
-
const dy = e.clientY - lastMouseRef.current.y;
|
|
3811
|
-
dragDistanceRef.current += Math.abs(dx) + Math.abs(dy);
|
|
3812
|
-
cameraRef.current.x -= dx;
|
|
3813
|
-
cameraRef.current.y -= dy;
|
|
3814
|
-
lastMouseRef.current = { x: e.clientX, y: e.clientY };
|
|
3815
|
-
targetCameraRef.current = null;
|
|
3816
|
-
drawFn?.();
|
|
3817
|
-
return dragDistanceRef.current > 5;
|
|
3818
|
-
}, []);
|
|
3819
|
-
const handleMouseLeave = useCallback(() => {
|
|
3820
|
-
isDraggingRef.current = false;
|
|
3821
|
-
}, []);
|
|
3822
|
-
const handleWheel = useCallback((e, drawFn) => {
|
|
3823
|
-
e.preventDefault();
|
|
3824
|
-
const zoomDelta = e.deltaY > 0 ? 0.9 : 1.1;
|
|
3825
|
-
cameraRef.current.zoom = Math.max(0.5, Math.min(3, cameraRef.current.zoom * zoomDelta));
|
|
3826
|
-
drawFn?.();
|
|
3827
|
-
}, []);
|
|
3828
|
-
const screenToWorld = useCallback((clientX, clientY, canvas, viewportSize) => {
|
|
3829
|
-
const rect = canvas.getBoundingClientRect();
|
|
3830
|
-
const screenX = clientX - rect.left;
|
|
3831
|
-
const screenY = clientY - rect.top;
|
|
3832
|
-
const cam = cameraRef.current;
|
|
3833
|
-
const worldX = (screenX - viewportSize.width / 2) / cam.zoom + viewportSize.width / 2 + cam.x;
|
|
3834
|
-
const worldY = (screenY - viewportSize.height / 2) / cam.zoom + viewportSize.height / 2 + cam.y;
|
|
3835
|
-
return { x: worldX, y: worldY };
|
|
3836
|
-
}, []);
|
|
3837
|
-
const lerpToTarget = useCallback((t = 0.08) => {
|
|
3838
|
-
if (!targetCameraRef.current) return false;
|
|
3839
|
-
const cam = cameraRef.current;
|
|
3840
|
-
cam.x += (targetCameraRef.current.x - cam.x) * t;
|
|
3841
|
-
cam.y += (targetCameraRef.current.y - cam.y) * t;
|
|
3842
|
-
if (Math.abs(cam.x - targetCameraRef.current.x) < 0.5 && Math.abs(cam.y - targetCameraRef.current.y) < 0.5) {
|
|
3843
|
-
cam.x = targetCameraRef.current.x;
|
|
3844
|
-
cam.y = targetCameraRef.current.y;
|
|
3845
|
-
targetCameraRef.current = null;
|
|
3846
|
-
}
|
|
3847
|
-
return true;
|
|
3848
|
-
}, []);
|
|
3849
|
-
return {
|
|
3850
|
-
cameraRef,
|
|
3851
|
-
targetCameraRef,
|
|
3852
|
-
isDragging: () => isDraggingRef.current,
|
|
3853
|
-
dragDistance: () => dragDistanceRef.current,
|
|
3854
|
-
handleMouseDown,
|
|
3855
|
-
handleMouseUp,
|
|
3856
|
-
handleMouseMove,
|
|
3857
|
-
handleMouseLeave,
|
|
3858
|
-
handleWheel,
|
|
3859
|
-
screenToWorld,
|
|
3860
|
-
lerpToTarget
|
|
3861
|
-
};
|
|
3862
|
-
}
|
|
3863
|
-
|
|
3864
|
-
// components/organisms/game/utils/isometric.ts
|
|
3865
|
-
var TILE_WIDTH = 256;
|
|
3866
|
-
var TILE_HEIGHT = 512;
|
|
3867
|
-
var FLOOR_HEIGHT = 128;
|
|
3868
|
-
var DIAMOND_TOP_Y = 374;
|
|
3869
|
-
var FEATURE_COLORS = {
|
|
3870
|
-
goldMine: "#fbbf24",
|
|
3871
|
-
resonanceCrystal: "#a78bfa",
|
|
3872
|
-
traitCache: "#60a5fa",
|
|
3873
|
-
salvageYard: "#6b7280",
|
|
3874
|
-
portal: "#c084fc",
|
|
3875
|
-
castle: "#f59e0b",
|
|
3876
|
-
mountain: "#78716c",
|
|
3877
|
-
default: "#9ca3af"
|
|
3878
|
-
};
|
|
3879
|
-
function isoToScreen(tileX, tileY, scale, baseOffsetX) {
|
|
3880
|
-
const scaledTileWidth = TILE_WIDTH * scale;
|
|
3881
|
-
const scaledFloorHeight = FLOOR_HEIGHT * scale;
|
|
3882
|
-
const screenX = (tileX - tileY) * (scaledTileWidth / 2) + baseOffsetX;
|
|
3883
|
-
const screenY = (tileX + tileY) * (scaledFloorHeight / 2);
|
|
3884
|
-
return { x: screenX, y: screenY };
|
|
3885
|
-
}
|
|
3886
|
-
function screenToIso(screenX, screenY, scale, baseOffsetX) {
|
|
3887
|
-
const scaledTileWidth = TILE_WIDTH * scale;
|
|
3888
|
-
const scaledFloorHeight = FLOOR_HEIGHT * scale;
|
|
3889
|
-
const adjustedX = screenX - baseOffsetX;
|
|
3890
|
-
const tileX = (adjustedX / (scaledTileWidth / 2) + screenY / (scaledFloorHeight / 2)) / 2;
|
|
3891
|
-
const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
|
|
3892
|
-
return { x: Math.round(tileX), y: Math.round(tileY) };
|
|
3893
|
-
}
|
|
3894
|
-
function IsometricCanvas({
|
|
3895
|
-
// Closed-circuit
|
|
3896
|
-
className,
|
|
3897
|
-
isLoading = false,
|
|
3898
|
-
error = null,
|
|
3899
|
-
entity,
|
|
3900
|
-
// Grid data
|
|
3901
|
-
tiles: tilesProp = [],
|
|
3902
|
-
units = [],
|
|
3903
|
-
features = [],
|
|
3904
|
-
// Interaction state
|
|
3905
|
-
selectedUnitId = null,
|
|
3906
|
-
validMoves = [],
|
|
3907
|
-
attackTargets = [],
|
|
3908
|
-
hoveredTile = null,
|
|
3909
|
-
// Event handlers
|
|
3910
|
-
onTileClick,
|
|
3911
|
-
onUnitClick,
|
|
3912
|
-
onTileHover,
|
|
3913
|
-
onTileLeave,
|
|
3914
|
-
// Declarative event props
|
|
3915
|
-
tileClickEvent,
|
|
3916
|
-
unitClickEvent,
|
|
3917
|
-
tileHoverEvent,
|
|
3918
|
-
tileLeaveEvent,
|
|
3919
|
-
// Rendering options
|
|
3920
|
-
scale = 0.4,
|
|
3921
|
-
debug = false,
|
|
3922
|
-
backgroundImage,
|
|
3923
|
-
showMinimap = true,
|
|
3924
|
-
enableCamera = true,
|
|
3925
|
-
unitScale = 1,
|
|
3926
|
-
// Asset resolution
|
|
3927
|
-
getTerrainSprite,
|
|
3928
|
-
getFeatureSprite,
|
|
3929
|
-
getUnitSprite,
|
|
3930
|
-
resolveUnitFrame,
|
|
3931
|
-
effectSpriteUrls = [],
|
|
3932
|
-
onDrawEffects,
|
|
3933
|
-
hasActiveEffects: hasActiveEffects2 = false,
|
|
3934
|
-
// Tuning
|
|
3935
|
-
diamondTopY: diamondTopYProp,
|
|
3936
|
-
// Remote asset loading
|
|
3937
|
-
assetBaseUrl,
|
|
3938
|
-
assetManifest
|
|
3939
|
-
}) {
|
|
3940
|
-
const eventBus = useEventBus();
|
|
3941
|
-
const { t } = useTranslate();
|
|
3942
|
-
const canvasRef = useRef(null);
|
|
3943
|
-
const containerRef = useRef(null);
|
|
3944
|
-
const minimapRef = useRef(null);
|
|
3945
|
-
const animTimeRef = useRef(0);
|
|
3946
|
-
const rafIdRef = useRef(0);
|
|
3947
|
-
const [viewportSize, setViewportSize] = useState({ width: 800, height: 600 });
|
|
3948
|
-
useEffect(() => {
|
|
3949
|
-
const el = containerRef.current;
|
|
3950
|
-
if (!el) return;
|
|
3951
|
-
if (typeof ResizeObserver === "undefined") return;
|
|
3952
|
-
const observer = new ResizeObserver((entries) => {
|
|
3953
|
-
const entry = entries[0];
|
|
3954
|
-
if (entry) {
|
|
3955
|
-
setViewportSize({
|
|
3956
|
-
width: entry.contentRect.width || 800,
|
|
3957
|
-
height: entry.contentRect.height || 600
|
|
3958
|
-
});
|
|
3959
|
-
}
|
|
3960
|
-
});
|
|
3961
|
-
observer.observe(el);
|
|
3962
|
-
return () => observer.disconnect();
|
|
3963
|
-
}, []);
|
|
3964
|
-
const sortedTiles = useMemo(() => {
|
|
3965
|
-
const tiles = [...tilesProp];
|
|
3966
|
-
tiles.sort((a, b) => {
|
|
3967
|
-
const depthA = a.x + a.y;
|
|
3968
|
-
const depthB = b.x + b.y;
|
|
3969
|
-
return depthA !== depthB ? depthA - depthB : a.y - b.y;
|
|
3970
|
-
});
|
|
3971
|
-
return tiles;
|
|
3972
|
-
}, [tilesProp]);
|
|
3973
|
-
const gridWidth = useMemo(() => {
|
|
3974
|
-
if (sortedTiles.length === 0) return 0;
|
|
3975
|
-
return Math.max(...sortedTiles.map((t2) => t2.x)) + 1;
|
|
3976
|
-
}, [sortedTiles]);
|
|
3977
|
-
const gridHeight = useMemo(() => {
|
|
3978
|
-
if (sortedTiles.length === 0) return 0;
|
|
3979
|
-
return Math.max(...sortedTiles.map((t2) => t2.y)) + 1;
|
|
3980
|
-
}, [sortedTiles]);
|
|
3981
|
-
const scaledTileWidth = TILE_WIDTH * scale;
|
|
3982
|
-
const scaledTileHeight = TILE_HEIGHT * scale;
|
|
3983
|
-
const scaledFloorHeight = FLOOR_HEIGHT * scale;
|
|
3984
|
-
const effectiveDiamondTopY = diamondTopYProp ?? DIAMOND_TOP_Y;
|
|
3985
|
-
const scaledDiamondTopY = effectiveDiamondTopY * scale;
|
|
3986
|
-
const baseOffsetX = useMemo(() => {
|
|
3987
|
-
return (gridHeight - 1) * (scaledTileWidth / 2);
|
|
3988
|
-
}, [gridHeight, scaledTileWidth]);
|
|
3989
|
-
const validMoveSet = useMemo(() => {
|
|
3990
|
-
return new Set(validMoves.map((p2) => `${p2.x},${p2.y}`));
|
|
3991
|
-
}, [validMoves]);
|
|
3992
|
-
const attackTargetSet = useMemo(() => {
|
|
3993
|
-
return new Set(attackTargets.map((p2) => `${p2.x},${p2.y}`));
|
|
3994
|
-
}, [attackTargets]);
|
|
3995
|
-
const resolveManifestUrl = useCallback((relativePath) => {
|
|
3996
|
-
if (!relativePath) return void 0;
|
|
3997
|
-
if (assetBaseUrl) return `${assetBaseUrl.replace(/\/$/, "")}${relativePath.startsWith("/") ? "" : "/"}${relativePath}`;
|
|
3998
|
-
return relativePath;
|
|
3999
|
-
}, [assetBaseUrl]);
|
|
4000
|
-
const spriteUrls = useMemo(() => {
|
|
4001
|
-
const urls = [];
|
|
4002
|
-
for (const tile of sortedTiles) {
|
|
4003
|
-
if (tile.terrainSprite) urls.push(tile.terrainSprite);
|
|
4004
|
-
else if (getTerrainSprite) {
|
|
4005
|
-
const url = getTerrainSprite(tile.terrain ?? "");
|
|
4006
|
-
if (url) urls.push(url);
|
|
4007
|
-
} else {
|
|
4008
|
-
const url = resolveManifestUrl(assetManifest?.terrains?.[tile.terrain ?? ""]);
|
|
4009
|
-
if (url) urls.push(url);
|
|
4010
|
-
}
|
|
4011
|
-
}
|
|
4012
|
-
for (const feature of features) {
|
|
4013
|
-
if (feature.sprite) urls.push(feature.sprite);
|
|
4014
|
-
else if (getFeatureSprite) {
|
|
4015
|
-
const url = getFeatureSprite(feature.type);
|
|
4016
|
-
if (url) urls.push(url);
|
|
4017
|
-
} else {
|
|
4018
|
-
const url = resolveManifestUrl(assetManifest?.features?.[feature.type]);
|
|
4019
|
-
if (url) urls.push(url);
|
|
4020
|
-
}
|
|
4021
|
-
}
|
|
4022
|
-
for (const unit of units) {
|
|
4023
|
-
if (unit.sprite) urls.push(unit.sprite);
|
|
4024
|
-
else if (getUnitSprite) {
|
|
4025
|
-
const url = getUnitSprite(unit);
|
|
4026
|
-
if (url) urls.push(url);
|
|
4027
|
-
} else if (unit.unitType) {
|
|
4028
|
-
const url = resolveManifestUrl(assetManifest?.units?.[unit.unitType]);
|
|
4029
|
-
if (url) urls.push(url);
|
|
4030
|
-
}
|
|
4031
|
-
}
|
|
4032
|
-
if (assetManifest?.effects) {
|
|
4033
|
-
for (const path of Object.values(assetManifest.effects)) {
|
|
4034
|
-
const url = resolveManifestUrl(path);
|
|
4035
|
-
if (url) urls.push(url);
|
|
4036
|
-
}
|
|
4037
|
-
}
|
|
4038
|
-
urls.push(...effectSpriteUrls);
|
|
4039
|
-
if (backgroundImage) urls.push(backgroundImage);
|
|
4040
|
-
return [...new Set(urls.filter(Boolean))];
|
|
4041
|
-
}, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, backgroundImage, assetManifest, resolveManifestUrl]);
|
|
4042
|
-
const { getImage } = useImageCache(spriteUrls);
|
|
4043
|
-
const {
|
|
4044
|
-
cameraRef,
|
|
4045
|
-
targetCameraRef,
|
|
4046
|
-
isDragging,
|
|
4047
|
-
dragDistance,
|
|
4048
|
-
handleMouseDown,
|
|
4049
|
-
handleMouseUp,
|
|
4050
|
-
handleMouseMove,
|
|
4051
|
-
handleMouseLeave,
|
|
4052
|
-
handleWheel,
|
|
4053
|
-
screenToWorld,
|
|
4054
|
-
lerpToTarget
|
|
4055
|
-
} = useCamera();
|
|
4056
|
-
const resolveTerrainSpriteUrl = useCallback((tile) => {
|
|
4057
|
-
return tile.terrainSprite || getTerrainSprite?.(tile.terrain ?? "") || resolveManifestUrl(assetManifest?.terrains?.[tile.terrain ?? ""]);
|
|
4058
|
-
}, [getTerrainSprite, assetManifest, resolveManifestUrl]);
|
|
4059
|
-
const resolveFeatureSpriteUrl = useCallback((featureType) => {
|
|
4060
|
-
return getFeatureSprite?.(featureType) || resolveManifestUrl(assetManifest?.features?.[featureType]);
|
|
4061
|
-
}, [getFeatureSprite, assetManifest, resolveManifestUrl]);
|
|
4062
|
-
const resolveUnitSpriteUrl = useCallback((unit) => {
|
|
4063
|
-
return unit.sprite || getUnitSprite?.(unit) || (unit.unitType ? resolveManifestUrl(assetManifest?.units?.[unit.unitType]) : void 0);
|
|
4064
|
-
}, [getUnitSprite, assetManifest, resolveManifestUrl]);
|
|
4065
|
-
const drawMinimap = useCallback(() => {
|
|
4066
|
-
if (!showMinimap) return;
|
|
4067
|
-
const miniCanvas = minimapRef.current;
|
|
4068
|
-
if (!miniCanvas || sortedTiles.length === 0) return;
|
|
4069
|
-
const mCtx = miniCanvas.getContext("2d");
|
|
4070
|
-
if (!mCtx) return;
|
|
4071
|
-
const mW = 150;
|
|
4072
|
-
const mH = 100;
|
|
4073
|
-
miniCanvas.width = mW;
|
|
4074
|
-
miniCanvas.height = mH;
|
|
4075
|
-
mCtx.clearRect(0, 0, mW, mH);
|
|
4076
|
-
const allScreenPos = sortedTiles.map((t2) => isoToScreen(t2.x, t2.y, scale, baseOffsetX));
|
|
4077
|
-
const minX = Math.min(...allScreenPos.map((p2) => p2.x));
|
|
4078
|
-
const maxX = Math.max(...allScreenPos.map((p2) => p2.x + scaledTileWidth));
|
|
4079
|
-
const minY = Math.min(...allScreenPos.map((p2) => p2.y));
|
|
4080
|
-
const maxY = Math.max(...allScreenPos.map((p2) => p2.y + scaledTileHeight));
|
|
4081
|
-
const worldW = maxX - minX;
|
|
4082
|
-
const worldH = maxY - minY;
|
|
4083
|
-
const scaleM = Math.min(mW / worldW, mH / worldH) * 0.9;
|
|
4084
|
-
const offsetMx = (mW - worldW * scaleM) / 2;
|
|
4085
|
-
const offsetMy = (mH - worldH * scaleM) / 2;
|
|
4086
|
-
for (const tile of sortedTiles) {
|
|
4087
|
-
const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
|
|
4088
|
-
const mx = (pos.x - minX) * scaleM + offsetMx;
|
|
4089
|
-
const my = (pos.y - minY) * scaleM + offsetMy;
|
|
4090
|
-
const mTileW = scaledTileWidth * scaleM;
|
|
4091
|
-
const mFloorH = scaledFloorHeight * scaleM;
|
|
4092
|
-
mCtx.fillStyle = tile.terrain === "water" ? "#3b82f6" : tile.terrain === "mountain" ? "#78716c" : "#4ade80";
|
|
4093
|
-
mCtx.beginPath();
|
|
4094
|
-
mCtx.moveTo(mx + mTileW / 2, my);
|
|
4095
|
-
mCtx.lineTo(mx + mTileW, my + mFloorH / 2);
|
|
4096
|
-
mCtx.lineTo(mx + mTileW / 2, my + mFloorH);
|
|
4097
|
-
mCtx.lineTo(mx, my + mFloorH / 2);
|
|
4098
|
-
mCtx.closePath();
|
|
4099
|
-
mCtx.fill();
|
|
4100
|
-
}
|
|
4101
|
-
for (const unit of units) {
|
|
4102
|
-
if (!unit.position) continue;
|
|
4103
|
-
const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
|
|
4104
|
-
const mx = (pos.x + scaledTileWidth / 2 - minX) * scaleM + offsetMx;
|
|
4105
|
-
const my = (pos.y + scaledTileHeight / 2 - minY) * scaleM + offsetMy;
|
|
4106
|
-
mCtx.fillStyle = unit.team === "player" ? "#60a5fa" : unit.team === "enemy" ? "#f87171" : "#9ca3af";
|
|
4107
|
-
mCtx.beginPath();
|
|
4108
|
-
mCtx.arc(mx, my, 3, 0, Math.PI * 2);
|
|
4109
|
-
mCtx.fill();
|
|
4110
|
-
}
|
|
4111
|
-
const cam = cameraRef.current;
|
|
4112
|
-
const vLeft = (cam.x - minX) * scaleM + offsetMx;
|
|
4113
|
-
const vTop = (cam.y - minY) * scaleM + offsetMy;
|
|
4114
|
-
const vW = viewportSize.width / cam.zoom * scaleM;
|
|
4115
|
-
const vH = viewportSize.height / cam.zoom * scaleM;
|
|
4116
|
-
mCtx.strokeStyle = "rgba(255, 255, 255, 0.8)";
|
|
4117
|
-
mCtx.lineWidth = 1;
|
|
4118
|
-
mCtx.strokeRect(vLeft, vTop, vW, vH);
|
|
4119
|
-
}, [showMinimap, sortedTiles, units, scale, baseOffsetX, scaledTileWidth, scaledTileHeight, scaledFloorHeight, viewportSize, cameraRef]);
|
|
4120
|
-
const draw = useCallback((animTime) => {
|
|
4121
|
-
const canvas = canvasRef.current;
|
|
4122
|
-
if (!canvas) return;
|
|
4123
|
-
const ctx = canvas.getContext("2d");
|
|
4124
|
-
if (!ctx) return;
|
|
4125
|
-
const dpr = window.devicePixelRatio || 1;
|
|
4126
|
-
canvas.width = viewportSize.width * dpr;
|
|
4127
|
-
canvas.height = viewportSize.height * dpr;
|
|
4128
|
-
ctx.scale(dpr, dpr);
|
|
4129
|
-
ctx.clearRect(0, 0, viewportSize.width, viewportSize.height);
|
|
4130
|
-
if (backgroundImage) {
|
|
4131
|
-
const bgImg = getImage(backgroundImage);
|
|
4132
|
-
if (bgImg) {
|
|
4133
|
-
const cam2 = cameraRef.current;
|
|
4134
|
-
const patW = bgImg.naturalWidth;
|
|
4135
|
-
const patH = bgImg.naturalHeight;
|
|
4136
|
-
const startX = -(cam2.x % patW + patW) % patW;
|
|
4137
|
-
const startY = -(cam2.y % patH + patH) % patH;
|
|
4138
|
-
for (let y = startY - patH; y < viewportSize.height; y += patH) {
|
|
4139
|
-
for (let x = startX - patW; x < viewportSize.width; x += patW) {
|
|
4140
|
-
ctx.drawImage(bgImg, x, y);
|
|
4141
|
-
}
|
|
4142
|
-
}
|
|
4143
|
-
}
|
|
4144
|
-
} else {
|
|
4145
|
-
ctx.fillStyle = "#1a1a2e";
|
|
4146
|
-
ctx.fillRect(0, 0, viewportSize.width, viewportSize.height);
|
|
4147
|
-
}
|
|
4148
|
-
if (sortedTiles.length === 0) return;
|
|
4149
|
-
ctx.save();
|
|
4150
|
-
const cam = cameraRef.current;
|
|
4151
|
-
ctx.translate(viewportSize.width / 2, viewportSize.height / 2);
|
|
4152
|
-
ctx.scale(cam.zoom, cam.zoom);
|
|
4153
|
-
ctx.translate(-viewportSize.width / 2 - cam.x, -viewportSize.height / 2 - cam.y);
|
|
4154
|
-
const visLeft = cam.x - viewportSize.width / cam.zoom;
|
|
4155
|
-
const visRight = cam.x + viewportSize.width * 2 / cam.zoom;
|
|
4156
|
-
const visTop = cam.y - viewportSize.height / cam.zoom;
|
|
4157
|
-
const visBottom = cam.y + viewportSize.height * 2 / cam.zoom;
|
|
4158
|
-
for (const tile of sortedTiles) {
|
|
4159
|
-
const pos = isoToScreen(tile.x, tile.y, scale, baseOffsetX);
|
|
4160
|
-
if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
|
|
4161
|
-
continue;
|
|
4162
|
-
}
|
|
4163
|
-
const spriteUrl = resolveTerrainSpriteUrl(tile);
|
|
4164
|
-
const img = spriteUrl ? getImage(spriteUrl) : null;
|
|
4165
|
-
if (img) {
|
|
4166
|
-
ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
|
|
4167
|
-
} else {
|
|
4168
|
-
const centerX = pos.x + scaledTileWidth / 2;
|
|
4169
|
-
const topY = pos.y + scaledDiamondTopY;
|
|
4170
|
-
ctx.fillStyle = tile.terrain === "water" ? "#3b82f6" : tile.terrain === "mountain" ? "#78716c" : tile.terrain === "stone" ? "#9ca3af" : "#4ade80";
|
|
4171
|
-
ctx.beginPath();
|
|
4172
|
-
ctx.moveTo(centerX, topY);
|
|
4173
|
-
ctx.lineTo(pos.x + scaledTileWidth, topY + scaledFloorHeight / 2);
|
|
4174
|
-
ctx.lineTo(centerX, topY + scaledFloorHeight);
|
|
4175
|
-
ctx.lineTo(pos.x, topY + scaledFloorHeight / 2);
|
|
4176
|
-
ctx.closePath();
|
|
4177
|
-
ctx.fill();
|
|
4178
|
-
ctx.strokeStyle = "rgba(0,0,0,0.2)";
|
|
4179
|
-
ctx.lineWidth = 1;
|
|
4180
|
-
ctx.stroke();
|
|
4181
|
-
}
|
|
4182
|
-
if (hoveredTile && hoveredTile.x === tile.x && hoveredTile.y === tile.y) {
|
|
4183
|
-
const centerX = pos.x + scaledTileWidth / 2;
|
|
4184
|
-
const topY = pos.y + scaledDiamondTopY;
|
|
4185
|
-
ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
|
|
4186
|
-
ctx.beginPath();
|
|
4187
|
-
ctx.moveTo(centerX, topY);
|
|
4188
|
-
ctx.lineTo(pos.x + scaledTileWidth, topY + scaledFloorHeight / 2);
|
|
4189
|
-
ctx.lineTo(centerX, topY + scaledFloorHeight);
|
|
4190
|
-
ctx.lineTo(pos.x, topY + scaledFloorHeight / 2);
|
|
4191
|
-
ctx.closePath();
|
|
4192
|
-
ctx.fill();
|
|
4193
|
-
}
|
|
4194
|
-
const tileKey = `${tile.x},${tile.y}`;
|
|
4195
|
-
if (validMoveSet.has(tileKey)) {
|
|
4196
|
-
const centerX = pos.x + scaledTileWidth / 2;
|
|
4197
|
-
const topY = pos.y + scaledDiamondTopY;
|
|
4198
|
-
const pulse = 0.15 + 0.1 * Math.sin(animTime * 4e-3);
|
|
4199
|
-
ctx.fillStyle = `rgba(74, 222, 128, ${pulse})`;
|
|
4200
|
-
ctx.beginPath();
|
|
4201
|
-
ctx.moveTo(centerX, topY);
|
|
4202
|
-
ctx.lineTo(pos.x + scaledTileWidth, topY + scaledFloorHeight / 2);
|
|
4203
|
-
ctx.lineTo(centerX, topY + scaledFloorHeight);
|
|
4204
|
-
ctx.lineTo(pos.x, topY + scaledFloorHeight / 2);
|
|
4205
|
-
ctx.closePath();
|
|
4206
|
-
ctx.fill();
|
|
4207
|
-
}
|
|
4208
|
-
if (attackTargetSet.has(tileKey)) {
|
|
4209
|
-
const centerX = pos.x + scaledTileWidth / 2;
|
|
4210
|
-
const topY = pos.y + scaledDiamondTopY;
|
|
4211
|
-
const pulse = 0.2 + 0.15 * Math.sin(animTime * 5e-3);
|
|
4212
|
-
ctx.fillStyle = `rgba(239, 68, 68, ${pulse})`;
|
|
4213
|
-
ctx.beginPath();
|
|
4214
|
-
ctx.moveTo(centerX, topY);
|
|
4215
|
-
ctx.lineTo(pos.x + scaledTileWidth, topY + scaledFloorHeight / 2);
|
|
4216
|
-
ctx.lineTo(centerX, topY + scaledFloorHeight);
|
|
4217
|
-
ctx.lineTo(pos.x, topY + scaledFloorHeight / 2);
|
|
4218
|
-
ctx.closePath();
|
|
4219
|
-
ctx.fill();
|
|
4220
|
-
}
|
|
4221
|
-
if (debug) {
|
|
4222
|
-
const centerX = pos.x + scaledTileWidth / 2;
|
|
4223
|
-
const centerY = pos.y + scaledFloorHeight / 2 + scaledDiamondTopY;
|
|
4224
|
-
ctx.fillStyle = "rgba(0, 0, 0, 0.7)";
|
|
4225
|
-
ctx.font = `${12 * scale * 2}px monospace`;
|
|
4226
|
-
ctx.textAlign = "center";
|
|
4227
|
-
ctx.fillText(`${tile.x},${tile.y}`, centerX, centerY + 4);
|
|
4228
|
-
}
|
|
4229
|
-
}
|
|
4230
|
-
const sortedFeatures = [...features].sort((a, b) => {
|
|
4231
|
-
const depthA = a.x + a.y;
|
|
4232
|
-
const depthB = b.x + b.y;
|
|
4233
|
-
return depthA !== depthB ? depthA - depthB : a.y - b.y;
|
|
4234
|
-
});
|
|
4235
|
-
for (const feature of sortedFeatures) {
|
|
4236
|
-
const pos = isoToScreen(feature.x, feature.y, scale, baseOffsetX);
|
|
4237
|
-
if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
|
|
4238
|
-
continue;
|
|
4239
|
-
}
|
|
4240
|
-
const spriteUrl = feature.sprite || resolveFeatureSpriteUrl(feature.type);
|
|
4241
|
-
const img = spriteUrl ? getImage(spriteUrl) : null;
|
|
4242
|
-
const centerX = pos.x + scaledTileWidth / 2;
|
|
4243
|
-
const featureGroundY = pos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
|
|
4244
|
-
const isCastle = feature.type === "castle";
|
|
4245
|
-
const featureDrawH = isCastle ? scaledFloorHeight * 3.5 : scaledFloorHeight * 1.6;
|
|
4246
|
-
const maxFeatureW = isCastle ? scaledTileWidth * 1.8 : scaledTileWidth * 0.7;
|
|
4247
|
-
if (img) {
|
|
4248
|
-
const ar = img.naturalWidth / img.naturalHeight;
|
|
4249
|
-
let drawH = featureDrawH;
|
|
4250
|
-
let drawW = featureDrawH * ar;
|
|
4251
|
-
if (drawW > maxFeatureW) {
|
|
4252
|
-
drawW = maxFeatureW;
|
|
4253
|
-
drawH = maxFeatureW / ar;
|
|
4254
|
-
}
|
|
4255
|
-
const drawX = centerX - drawW / 2;
|
|
4256
|
-
const drawY = featureGroundY - drawH;
|
|
4257
|
-
ctx.drawImage(img, drawX, drawY, drawW, drawH);
|
|
4258
|
-
} else {
|
|
4259
|
-
const color = FEATURE_COLORS[feature.type] || FEATURE_COLORS.default;
|
|
4260
|
-
ctx.beginPath();
|
|
4261
|
-
ctx.arc(centerX, featureGroundY - 8 * scale, 16 * scale, 0, Math.PI * 2);
|
|
4262
|
-
ctx.fillStyle = color;
|
|
4263
|
-
ctx.fill();
|
|
4264
|
-
ctx.strokeStyle = "rgba(0,0,0,0.5)";
|
|
4265
|
-
ctx.lineWidth = 2;
|
|
4266
|
-
ctx.stroke();
|
|
4267
|
-
}
|
|
4268
|
-
}
|
|
4269
|
-
const unitsWithPosition = units.filter((u) => !!u.position);
|
|
4270
|
-
const sortedUnits = [...unitsWithPosition].sort((a, b) => {
|
|
4271
|
-
const depthA = a.position.x + a.position.y;
|
|
4272
|
-
const depthB = b.position.x + b.position.y;
|
|
4273
|
-
return depthA !== depthB ? depthA - depthB : a.position.y - b.position.y;
|
|
4274
|
-
});
|
|
4275
|
-
for (const unit of sortedUnits) {
|
|
4276
|
-
const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
|
|
4277
|
-
if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
|
|
4278
|
-
continue;
|
|
4279
|
-
}
|
|
4280
|
-
const isSelected = unit.id === selectedUnitId;
|
|
4281
|
-
const centerX = pos.x + scaledTileWidth / 2;
|
|
4282
|
-
const groundY = pos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
|
|
4283
|
-
const breatheOffset = 0.8 * scale * (1 + Math.sin(animTime * 2e-3 + (unit.position.x * 3.7 + unit.position.y * 5.3)));
|
|
4284
|
-
const unitSpriteUrl = resolveUnitSpriteUrl(unit);
|
|
4285
|
-
const img = unitSpriteUrl ? getImage(unitSpriteUrl) : null;
|
|
4286
|
-
const unitDrawH = scaledFloorHeight * 1.5 * unitScale;
|
|
4287
|
-
const maxUnitW = scaledTileWidth * 0.6 * unitScale;
|
|
4288
|
-
const ar = img ? img.naturalWidth / img.naturalHeight : 0.5;
|
|
4289
|
-
let drawH = unitDrawH;
|
|
4290
|
-
let drawW = unitDrawH * ar;
|
|
4291
|
-
if (drawW > maxUnitW) {
|
|
4292
|
-
drawW = maxUnitW;
|
|
4293
|
-
drawH = maxUnitW / ar;
|
|
4294
|
-
}
|
|
4295
|
-
if (unit.previousPosition && (unit.previousPosition.x !== unit.position.x || unit.previousPosition.y !== unit.position.y)) {
|
|
4296
|
-
const ghostPos = isoToScreen(unit.previousPosition.x, unit.previousPosition.y, scale, baseOffsetX);
|
|
4297
|
-
const ghostCenterX = ghostPos.x + scaledTileWidth / 2;
|
|
4298
|
-
const ghostGroundY = ghostPos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
|
|
4299
|
-
ctx.save();
|
|
4300
|
-
ctx.globalAlpha = 0.25;
|
|
4301
|
-
if (img) {
|
|
4302
|
-
ctx.drawImage(img, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
|
|
4303
|
-
} else {
|
|
4304
|
-
const color = unit.team === "player" ? "#3b82f6" : unit.team === "enemy" ? "#ef4444" : "#6b7280";
|
|
4305
|
-
ctx.beginPath();
|
|
4306
|
-
ctx.arc(ghostCenterX, ghostGroundY - 16 * scale, 20 * scale, 0, Math.PI * 2);
|
|
4307
|
-
ctx.fillStyle = color;
|
|
4308
|
-
ctx.fill();
|
|
4309
|
-
}
|
|
4310
|
-
ctx.restore();
|
|
4311
|
-
}
|
|
4312
|
-
if (isSelected) {
|
|
4313
|
-
const ringAlpha = 0.6 + 0.3 * Math.sin(animTime * 4e-3);
|
|
4314
|
-
ctx.beginPath();
|
|
4315
|
-
ctx.ellipse(centerX, groundY, drawW / 2 + 4 * scale, 12 * scale, 0, 0, Math.PI * 2);
|
|
4316
|
-
ctx.strokeStyle = `rgba(0, 200, 255, ${ringAlpha})`;
|
|
4317
|
-
ctx.lineWidth = 3;
|
|
4318
|
-
ctx.stroke();
|
|
4319
|
-
}
|
|
4320
|
-
const frame = resolveUnitFrame?.(unit.id) ?? null;
|
|
4321
|
-
const frameImg = frame ? getImage(frame.sheetUrl) : null;
|
|
4322
|
-
if (frame && frameImg) {
|
|
4323
|
-
const frameAr = frame.sw / frame.sh;
|
|
4324
|
-
let fDrawH = unitDrawH;
|
|
4325
|
-
let fDrawW = unitDrawH * frameAr;
|
|
4326
|
-
if (fDrawW > maxUnitW) {
|
|
4327
|
-
fDrawW = maxUnitW;
|
|
4328
|
-
fDrawH = maxUnitW / frameAr;
|
|
4329
|
-
}
|
|
4330
|
-
const spriteY = groundY - fDrawH - (frame.applyBreathing ? breatheOffset : 0);
|
|
4331
|
-
ctx.save();
|
|
4332
|
-
if (unit.team) {
|
|
4333
|
-
ctx.shadowColor = unit.team === "player" ? "rgba(0, 150, 255, 0.6)" : "rgba(255, 50, 50, 0.6)";
|
|
4334
|
-
ctx.shadowBlur = 12 * scale;
|
|
4335
|
-
}
|
|
4336
|
-
if (frame.flipX) {
|
|
4337
|
-
ctx.translate(centerX, 0);
|
|
4338
|
-
ctx.scale(-1, 1);
|
|
4339
|
-
ctx.drawImage(frameImg, frame.sx, frame.sy, frame.sw, frame.sh, -fDrawW / 2, spriteY, fDrawW, fDrawH);
|
|
4340
|
-
} else {
|
|
4341
|
-
ctx.drawImage(frameImg, frame.sx, frame.sy, frame.sw, frame.sh, centerX - fDrawW / 2, spriteY, fDrawW, fDrawH);
|
|
4342
|
-
}
|
|
4343
|
-
ctx.restore();
|
|
4344
|
-
} else if (img) {
|
|
4345
|
-
const spriteY = groundY - drawH - breatheOffset;
|
|
4346
|
-
if (unit.team) {
|
|
4347
|
-
ctx.save();
|
|
4348
|
-
ctx.shadowColor = unit.team === "player" ? "rgba(0, 150, 255, 0.6)" : "rgba(255, 50, 50, 0.6)";
|
|
4349
|
-
ctx.shadowBlur = 12 * scale;
|
|
4350
|
-
ctx.drawImage(img, centerX - drawW / 2, spriteY, drawW, drawH);
|
|
4351
|
-
ctx.restore();
|
|
4352
|
-
} else {
|
|
4353
|
-
ctx.drawImage(img, centerX - drawW / 2, spriteY, drawW, drawH);
|
|
4354
|
-
}
|
|
4355
|
-
} else {
|
|
4356
|
-
const color = unit.team === "player" ? "#3b82f6" : unit.team === "enemy" ? "#ef4444" : "#6b7280";
|
|
4357
|
-
ctx.beginPath();
|
|
4358
|
-
ctx.arc(centerX, groundY - 20 * scale - breatheOffset, 20 * scale, 0, Math.PI * 2);
|
|
4359
|
-
ctx.fillStyle = color;
|
|
4360
|
-
ctx.fill();
|
|
4361
|
-
ctx.strokeStyle = "rgba(255,255,255,0.8)";
|
|
4362
|
-
ctx.lineWidth = 2;
|
|
4363
|
-
ctx.stroke();
|
|
4364
|
-
}
|
|
4365
|
-
if (unit.name) {
|
|
4366
|
-
const labelBg = unit.team === "player" ? "rgba(59, 130, 246, 0.9)" : unit.team === "enemy" ? "rgba(239, 68, 68, 0.9)" : "rgba(107, 114, 128, 0.9)";
|
|
4367
|
-
ctx.font = `bold ${10 * scale * 2.5}px system-ui`;
|
|
4368
|
-
ctx.textAlign = "center";
|
|
4369
|
-
const textWidth = ctx.measureText(unit.name).width;
|
|
4370
|
-
const labelY = groundY + 14 * scale - breatheOffset;
|
|
4371
|
-
ctx.fillStyle = labelBg;
|
|
4372
|
-
ctx.beginPath();
|
|
4373
|
-
ctx.roundRect(centerX - textWidth / 2 - 6 * scale, labelY - 8 * scale, textWidth + 12 * scale, 16 * scale, 4 * scale);
|
|
4374
|
-
ctx.fill();
|
|
4375
|
-
ctx.fillStyle = "white";
|
|
4376
|
-
ctx.fillText(unit.name, centerX, labelY + 4 * scale);
|
|
4377
|
-
}
|
|
4378
|
-
if (unit.health !== void 0 && unit.maxHealth !== void 0) {
|
|
4379
|
-
const barWidth = 40 * scale;
|
|
4380
|
-
const barHeight = 6 * scale;
|
|
4381
|
-
const barX = centerX - barWidth / 2;
|
|
4382
|
-
const barY = groundY - drawH - 2 * scale - breatheOffset;
|
|
4383
|
-
const healthRatio = unit.health / unit.maxHealth;
|
|
4384
|
-
const barRadius = barHeight / 2;
|
|
4385
|
-
ctx.fillStyle = "rgba(0, 0, 0, 0.7)";
|
|
4386
|
-
ctx.beginPath();
|
|
4387
|
-
ctx.roundRect(barX, barY, barWidth, barHeight, barRadius);
|
|
4388
|
-
ctx.fill();
|
|
4389
|
-
if (healthRatio > 0) {
|
|
4390
|
-
const fillWidth = barWidth * healthRatio;
|
|
4391
|
-
const gradient = ctx.createLinearGradient(barX, barY, barX, barY + barHeight);
|
|
4392
|
-
if (healthRatio > 0.6) {
|
|
4393
|
-
gradient.addColorStop(0, "#4ade80");
|
|
4394
|
-
gradient.addColorStop(1, "#22c55e");
|
|
4395
|
-
} else if (healthRatio > 0.3) {
|
|
4396
|
-
gradient.addColorStop(0, "#fde047");
|
|
4397
|
-
gradient.addColorStop(1, "#eab308");
|
|
4398
|
-
} else {
|
|
4399
|
-
gradient.addColorStop(0, "#f87171");
|
|
4400
|
-
gradient.addColorStop(1, "#ef4444");
|
|
4401
|
-
}
|
|
4402
|
-
ctx.fillStyle = gradient;
|
|
4403
|
-
ctx.save();
|
|
4404
|
-
ctx.beginPath();
|
|
4405
|
-
ctx.roundRect(barX, barY, fillWidth, barHeight, barRadius);
|
|
4406
|
-
ctx.clip();
|
|
4407
|
-
ctx.fillRect(barX, barY, fillWidth, barHeight);
|
|
4408
|
-
ctx.restore();
|
|
4409
|
-
}
|
|
4410
|
-
ctx.strokeStyle = "rgba(255, 255, 255, 0.3)";
|
|
4411
|
-
ctx.lineWidth = 1;
|
|
4412
|
-
ctx.beginPath();
|
|
4413
|
-
ctx.roundRect(barX, barY, barWidth, barHeight, barRadius);
|
|
4414
|
-
ctx.stroke();
|
|
4415
|
-
}
|
|
4416
|
-
}
|
|
4417
|
-
onDrawEffects?.(ctx, animTime, getImage);
|
|
4418
|
-
ctx.restore();
|
|
4419
|
-
drawMinimap();
|
|
4420
|
-
}, [
|
|
4421
|
-
sortedTiles,
|
|
4422
|
-
units,
|
|
4423
|
-
features,
|
|
4424
|
-
selectedUnitId,
|
|
4425
|
-
scale,
|
|
4426
|
-
debug,
|
|
4427
|
-
resolveTerrainSpriteUrl,
|
|
4428
|
-
resolveFeatureSpriteUrl,
|
|
4429
|
-
resolveUnitSpriteUrl,
|
|
4430
|
-
resolveUnitFrame,
|
|
4431
|
-
getImage,
|
|
4432
|
-
gridWidth,
|
|
4433
|
-
gridHeight,
|
|
4434
|
-
baseOffsetX,
|
|
4435
|
-
scaledTileWidth,
|
|
4436
|
-
scaledTileHeight,
|
|
4437
|
-
scaledFloorHeight,
|
|
4438
|
-
scaledDiamondTopY,
|
|
4439
|
-
validMoveSet,
|
|
4440
|
-
attackTargetSet,
|
|
4441
|
-
hoveredTile,
|
|
4442
|
-
viewportSize,
|
|
4443
|
-
drawMinimap,
|
|
4444
|
-
onDrawEffects,
|
|
4445
|
-
backgroundImage,
|
|
4446
|
-
cameraRef,
|
|
4447
|
-
unitScale
|
|
4448
|
-
]);
|
|
4449
|
-
useEffect(() => {
|
|
4450
|
-
if (!selectedUnitId) return;
|
|
4451
|
-
const unit = units.find((u) => u.id === selectedUnitId);
|
|
4452
|
-
if (!unit?.position) return;
|
|
4453
|
-
const pos = isoToScreen(unit.position.x, unit.position.y, scale, baseOffsetX);
|
|
4454
|
-
const centerX = pos.x + scaledTileWidth / 2;
|
|
4455
|
-
const centerY = pos.y + scaledDiamondTopY + scaledFloorHeight / 2;
|
|
4456
|
-
targetCameraRef.current = {
|
|
4457
|
-
x: centerX - viewportSize.width / 2,
|
|
4458
|
-
y: centerY - viewportSize.height / 2
|
|
4459
|
-
};
|
|
4460
|
-
}, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
|
|
4461
|
-
useEffect(() => {
|
|
4462
|
-
const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2;
|
|
4463
|
-
draw(animTimeRef.current);
|
|
4464
|
-
if (!hasAnimations) return;
|
|
4465
|
-
let running = true;
|
|
4466
|
-
const animate = (timestamp) => {
|
|
4467
|
-
if (!running) return;
|
|
4468
|
-
animTimeRef.current = timestamp;
|
|
4469
|
-
lerpToTarget();
|
|
4470
|
-
draw(timestamp);
|
|
4471
|
-
rafIdRef.current = requestAnimationFrame(animate);
|
|
4472
|
-
};
|
|
4473
|
-
rafIdRef.current = requestAnimationFrame(animate);
|
|
4474
|
-
return () => {
|
|
4475
|
-
running = false;
|
|
4476
|
-
cancelAnimationFrame(rafIdRef.current);
|
|
4477
|
-
};
|
|
4478
|
-
}, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId, hasActiveEffects2, lerpToTarget, targetCameraRef]);
|
|
4479
|
-
const handleMouseMoveWithCamera = useCallback((e) => {
|
|
4480
|
-
if (enableCamera) {
|
|
4481
|
-
const wasPanning = handleMouseMove(e, () => draw(animTimeRef.current));
|
|
4482
|
-
if (wasPanning) return;
|
|
4483
|
-
}
|
|
4484
|
-
if (!onTileHover && !tileHoverEvent || !canvasRef.current) return;
|
|
4485
|
-
const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
|
|
4486
|
-
const adjustedX = world.x - scaledTileWidth / 2;
|
|
4487
|
-
const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
|
|
4488
|
-
const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
|
|
4489
|
-
const tileExists = tilesProp.some((t2) => t2.x === isoPos.x && t2.y === isoPos.y);
|
|
4490
|
-
if (tileExists) {
|
|
4491
|
-
if (tileHoverEvent) eventBus.emit(`UI:${tileHoverEvent}`, { x: isoPos.x, y: isoPos.y });
|
|
4492
|
-
onTileHover?.(isoPos.x, isoPos.y);
|
|
4493
|
-
}
|
|
4494
|
-
}, [enableCamera, handleMouseMove, draw, onTileHover, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, tilesProp, tileHoverEvent, eventBus]);
|
|
4495
|
-
const handleMouseLeaveWithCamera = useCallback(() => {
|
|
4496
|
-
handleMouseLeave();
|
|
4497
|
-
if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
|
|
4498
|
-
onTileLeave?.();
|
|
4499
|
-
}, [handleMouseLeave, onTileLeave, tileLeaveEvent, eventBus]);
|
|
4500
|
-
const handleWheelWithCamera = useCallback((e) => {
|
|
4501
|
-
if (enableCamera) {
|
|
4502
|
-
handleWheel(e, () => draw(animTimeRef.current));
|
|
4503
|
-
}
|
|
4504
|
-
}, [enableCamera, handleWheel, draw]);
|
|
4505
|
-
const handleClick = useCallback((e) => {
|
|
4506
|
-
if (dragDistance() > 5) return;
|
|
4507
|
-
if (!canvasRef.current) return;
|
|
4508
|
-
const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
|
|
4509
|
-
const adjustedX = world.x - scaledTileWidth / 2;
|
|
4510
|
-
const adjustedY = world.y - scaledDiamondTopY - scaledFloorHeight / 2;
|
|
4511
|
-
const isoPos = screenToIso(adjustedX, adjustedY, scale, baseOffsetX);
|
|
4512
|
-
const clickedUnit = units.find((u) => u.position?.x === isoPos.x && u.position?.y === isoPos.y);
|
|
4513
|
-
if (clickedUnit && (onUnitClick || unitClickEvent)) {
|
|
4514
|
-
if (unitClickEvent) eventBus.emit(`UI:${unitClickEvent}`, { unitId: clickedUnit.id });
|
|
4515
|
-
onUnitClick?.(clickedUnit.id);
|
|
4516
|
-
} else if (onTileClick || tileClickEvent) {
|
|
4517
|
-
const tileExists = tilesProp.some((t2) => t2.x === isoPos.x && t2.y === isoPos.y);
|
|
4518
|
-
if (tileExists) {
|
|
4519
|
-
if (tileClickEvent) eventBus.emit(`UI:${tileClickEvent}`, { x: isoPos.x, y: isoPos.y });
|
|
4520
|
-
onTileClick?.(isoPos.x, isoPos.y);
|
|
4521
|
-
}
|
|
4522
|
-
}
|
|
4523
|
-
}, [dragDistance, screenToWorld, viewportSize, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, scale, baseOffsetX, units, tilesProp, onUnitClick, onTileClick, unitClickEvent, tileClickEvent, eventBus]);
|
|
4524
|
-
if (error) {
|
|
4525
|
-
return /* @__PURE__ */ jsx(ErrorState, { title: t("canvas.errorTitle"), message: error.message, className });
|
|
4526
|
-
}
|
|
4527
|
-
if (isLoading) {
|
|
4528
|
-
return /* @__PURE__ */ jsx(LoadingState, { className });
|
|
4529
|
-
}
|
|
4530
|
-
if (sortedTiles.length === 0) {
|
|
4531
|
-
return /* @__PURE__ */ jsx(
|
|
4532
|
-
Box,
|
|
4533
|
-
{
|
|
4534
|
-
className: cn("relative w-full overflow-hidden rounded-lg", className),
|
|
4535
|
-
style: { height: viewportSize.height },
|
|
4536
|
-
"data-testid": "game-canvas-empty",
|
|
4537
|
-
children: /* @__PURE__ */ jsx(Box, { className: "flex items-center justify-center h-full bg-slate-800 rounded-lg", children: /* @__PURE__ */ jsxs(Stack, { direction: "vertical", gap: "md", align: "center", children: [
|
|
4538
|
-
/* @__PURE__ */ jsx(Icon, { name: "map", size: "xl" }),
|
|
4539
|
-
/* @__PURE__ */ jsx(Typography, { variant: "body", className: "text-slate-400", children: t("canvas.emptyMessage") || "No map data loaded" })
|
|
4540
|
-
] }) })
|
|
4541
|
-
}
|
|
4542
|
-
);
|
|
4543
|
-
}
|
|
4544
|
-
return /* @__PURE__ */ jsxs(
|
|
4545
|
-
Box,
|
|
4546
|
-
{
|
|
4547
|
-
ref: containerRef,
|
|
4548
|
-
className: cn("relative overflow-hidden w-full h-full", className),
|
|
4549
|
-
children: [
|
|
4550
|
-
/* @__PURE__ */ jsx(
|
|
4551
|
-
"canvas",
|
|
4552
|
-
{
|
|
4553
|
-
ref: canvasRef,
|
|
4554
|
-
"data-testid": "game-canvas",
|
|
4555
|
-
onClick: handleClick,
|
|
4556
|
-
onMouseDown: enableCamera ? handleMouseDown : void 0,
|
|
4557
|
-
onMouseMove: handleMouseMoveWithCamera,
|
|
4558
|
-
onMouseUp: enableCamera ? handleMouseUp : void 0,
|
|
4559
|
-
onMouseLeave: handleMouseLeaveWithCamera,
|
|
4560
|
-
onWheel: handleWheelWithCamera,
|
|
4561
|
-
onContextMenu: (e) => e.preventDefault(),
|
|
4562
|
-
className: "cursor-pointer",
|
|
4563
|
-
style: {
|
|
4564
|
-
width: viewportSize.width,
|
|
4565
|
-
height: viewportSize.height
|
|
4566
|
-
}
|
|
4567
|
-
}
|
|
4568
|
-
),
|
|
4569
|
-
process.env.NODE_ENV !== "production" && /* @__PURE__ */ jsxs("div", { "data-game-actions": "", className: "sr-only", "aria-hidden": "true", children: [
|
|
4570
|
-
tileClickEvent && /* @__PURE__ */ jsx(
|
|
4571
|
-
"button",
|
|
4572
|
-
{
|
|
4573
|
-
"data-event": tileClickEvent,
|
|
4574
|
-
"data-x": "0",
|
|
4575
|
-
"data-y": "0",
|
|
4576
|
-
onClick: () => eventBus.emit(`UI:${tileClickEvent}`, { x: 0, y: 0 }),
|
|
4577
|
-
children: tileClickEvent
|
|
4578
|
-
}
|
|
4579
|
-
),
|
|
4580
|
-
unitClickEvent && units && units.length > 0 && /* @__PURE__ */ jsx(
|
|
4581
|
-
"button",
|
|
4582
|
-
{
|
|
4583
|
-
"data-event": unitClickEvent,
|
|
4584
|
-
"data-unit-id": units[0].id,
|
|
4585
|
-
onClick: () => eventBus.emit(`UI:${unitClickEvent}`, { unitId: units[0].id }),
|
|
4586
|
-
children: unitClickEvent
|
|
4587
|
-
}
|
|
4588
|
-
)
|
|
4589
|
-
] }),
|
|
4590
|
-
showMinimap && /* @__PURE__ */ jsx(
|
|
4591
|
-
"canvas",
|
|
4592
|
-
{
|
|
4593
|
-
ref: minimapRef,
|
|
4594
|
-
className: "absolute bottom-2 right-2 border border-border rounded bg-background/80 pointer-events-none",
|
|
4595
|
-
style: { width: 150, height: 100, zIndex: 10 }
|
|
4596
|
-
}
|
|
4597
|
-
)
|
|
4598
|
-
]
|
|
4599
|
-
}
|
|
4600
|
-
);
|
|
4601
|
-
}
|
|
4602
|
-
IsometricCanvas.displayName = "IsometricCanvas";
|
|
4603
|
-
var IsometricCanvas_default = IsometricCanvas;
|
|
4604
|
-
var PLATFORM_COLORS = {
|
|
4605
|
-
ground: "#4a7c59",
|
|
4606
|
-
platform: "#7c6b4a",
|
|
4607
|
-
hazard: "#c0392b",
|
|
4608
|
-
goal: "#f1c40f"
|
|
4609
|
-
};
|
|
4610
|
-
var PLAYER_COLOR = "#3498db";
|
|
4611
|
-
var PLAYER_EYE_COLOR = "#ffffff";
|
|
4612
|
-
var SKY_GRADIENT_TOP = "#1a1a2e";
|
|
4613
|
-
var SKY_GRADIENT_BOTTOM = "#16213e";
|
|
4614
|
-
var GRID_COLOR = "rgba(255, 255, 255, 0.03)";
|
|
4615
|
-
function PlatformerCanvas({
|
|
4616
|
-
player,
|
|
4617
|
-
platforms = [],
|
|
4618
|
-
worldWidth = 800,
|
|
4619
|
-
worldHeight = 400,
|
|
4620
|
-
canvasWidth = 800,
|
|
4621
|
-
canvasHeight = 400,
|
|
4622
|
-
followCamera = true,
|
|
4623
|
-
bgColor,
|
|
4624
|
-
leftEvent = "MOVE_LEFT",
|
|
4625
|
-
rightEvent = "MOVE_RIGHT",
|
|
4626
|
-
jumpEvent = "JUMP",
|
|
4627
|
-
stopEvent = "STOP",
|
|
4628
|
-
className,
|
|
4629
|
-
entity
|
|
4630
|
-
}) {
|
|
4631
|
-
const canvasRef = useRef(null);
|
|
4632
|
-
const eventBus = useEventBus();
|
|
4633
|
-
const keysRef = useRef(/* @__PURE__ */ new Set());
|
|
4634
|
-
const resolvedPlayer = player ?? {
|
|
4635
|
-
x: entity?.x ?? 50,
|
|
4636
|
-
y: entity?.y ?? 300,
|
|
4637
|
-
width: entity?.width ?? 24,
|
|
4638
|
-
height: entity?.height ?? 32,
|
|
4639
|
-
vx: entity?.vx ?? 0,
|
|
4640
|
-
vy: entity?.vy ?? 0,
|
|
4641
|
-
grounded: entity?.grounded ?? false,
|
|
4642
|
-
facingRight: entity?.facingRight ?? true
|
|
4643
|
-
};
|
|
4644
|
-
const handleKeyDown = useCallback((e) => {
|
|
4645
|
-
if (keysRef.current.has(e.code)) return;
|
|
4646
|
-
keysRef.current.add(e.code);
|
|
4647
|
-
switch (e.code) {
|
|
4648
|
-
case "ArrowLeft":
|
|
4649
|
-
case "KeyA":
|
|
4650
|
-
eventBus.emit(`UI:${leftEvent}`, { direction: -1 });
|
|
4651
|
-
break;
|
|
4652
|
-
case "ArrowRight":
|
|
4653
|
-
case "KeyD":
|
|
4654
|
-
eventBus.emit(`UI:${rightEvent}`, { direction: 1 });
|
|
4655
|
-
break;
|
|
4656
|
-
case "ArrowUp":
|
|
4657
|
-
case "KeyW":
|
|
4658
|
-
case "Space":
|
|
4659
|
-
eventBus.emit(`UI:${jumpEvent}`, {});
|
|
4660
|
-
e.preventDefault();
|
|
4661
|
-
break;
|
|
4662
|
-
}
|
|
4663
|
-
}, [eventBus, leftEvent, rightEvent, jumpEvent]);
|
|
4664
|
-
const handleKeyUp = useCallback((e) => {
|
|
4665
|
-
keysRef.current.delete(e.code);
|
|
4666
|
-
switch (e.code) {
|
|
4667
|
-
case "ArrowLeft":
|
|
4668
|
-
case "KeyA":
|
|
4669
|
-
case "ArrowRight":
|
|
4670
|
-
case "KeyD":
|
|
4671
|
-
eventBus.emit(`UI:${stopEvent}`, {});
|
|
4672
|
-
break;
|
|
4673
|
-
}
|
|
4674
|
-
}, [eventBus, stopEvent]);
|
|
4675
|
-
useEffect(() => {
|
|
4676
|
-
window.addEventListener("keydown", handleKeyDown);
|
|
4677
|
-
window.addEventListener("keyup", handleKeyUp);
|
|
4678
|
-
return () => {
|
|
4679
|
-
window.removeEventListener("keydown", handleKeyDown);
|
|
4680
|
-
window.removeEventListener("keyup", handleKeyUp);
|
|
4681
|
-
};
|
|
4682
|
-
}, [handleKeyDown, handleKeyUp]);
|
|
4683
|
-
useEffect(() => {
|
|
4684
|
-
const canvas = canvasRef.current;
|
|
4685
|
-
if (!canvas) return;
|
|
4686
|
-
const ctx = canvas.getContext("2d");
|
|
4687
|
-
if (!ctx) return;
|
|
4688
|
-
const dpr = window.devicePixelRatio || 1;
|
|
4689
|
-
canvas.width = canvasWidth * dpr;
|
|
4690
|
-
canvas.height = canvasHeight * dpr;
|
|
4691
|
-
ctx.scale(dpr, dpr);
|
|
4692
|
-
let camX = 0;
|
|
4693
|
-
let camY = 0;
|
|
4694
|
-
if (followCamera) {
|
|
4695
|
-
camX = Math.max(0, Math.min(resolvedPlayer.x - canvasWidth / 2, worldWidth - canvasWidth));
|
|
4696
|
-
camY = Math.max(0, Math.min(resolvedPlayer.y - canvasHeight / 2 - 50, worldHeight - canvasHeight));
|
|
4697
|
-
}
|
|
4698
|
-
if (bgColor) {
|
|
4699
|
-
ctx.fillStyle = bgColor;
|
|
4700
|
-
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
|
|
4701
|
-
} else {
|
|
4702
|
-
const grad = ctx.createLinearGradient(0, 0, 0, canvasHeight);
|
|
4703
|
-
grad.addColorStop(0, SKY_GRADIENT_TOP);
|
|
4704
|
-
grad.addColorStop(1, SKY_GRADIENT_BOTTOM);
|
|
4705
|
-
ctx.fillStyle = grad;
|
|
4706
|
-
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
|
|
4707
|
-
}
|
|
4708
|
-
ctx.strokeStyle = GRID_COLOR;
|
|
4709
|
-
ctx.lineWidth = 1;
|
|
4710
|
-
const gridSize = 32;
|
|
4711
|
-
for (let gx = -camX % gridSize; gx < canvasWidth; gx += gridSize) {
|
|
4712
|
-
ctx.beginPath();
|
|
4713
|
-
ctx.moveTo(gx, 0);
|
|
4714
|
-
ctx.lineTo(gx, canvasHeight);
|
|
4715
|
-
ctx.stroke();
|
|
4716
|
-
}
|
|
4717
|
-
for (let gy = -camY % gridSize; gy < canvasHeight; gy += gridSize) {
|
|
4718
|
-
ctx.beginPath();
|
|
4719
|
-
ctx.moveTo(0, gy);
|
|
4720
|
-
ctx.lineTo(canvasWidth, gy);
|
|
4721
|
-
ctx.stroke();
|
|
4722
|
-
}
|
|
4723
|
-
for (const plat of platforms) {
|
|
4724
|
-
const px = plat.x - camX;
|
|
4725
|
-
const py = plat.y - camY;
|
|
4726
|
-
const color = PLATFORM_COLORS[plat.type ?? "ground"] ?? PLATFORM_COLORS.ground;
|
|
4727
|
-
ctx.fillStyle = color;
|
|
4728
|
-
ctx.fillRect(px, py, plat.width, plat.height);
|
|
4729
|
-
ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
|
|
4730
|
-
ctx.fillRect(px, py, plat.width, 3);
|
|
4731
|
-
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
|
|
4732
|
-
ctx.fillRect(px, py + plat.height - 2, plat.width, 2);
|
|
4733
|
-
if (plat.type === "hazard") {
|
|
4734
|
-
ctx.strokeStyle = "#e74c3c";
|
|
4735
|
-
ctx.lineWidth = 2;
|
|
4736
|
-
for (let sx = px; sx < px + plat.width; sx += 12) {
|
|
4737
|
-
ctx.beginPath();
|
|
4738
|
-
ctx.moveTo(sx, py);
|
|
4739
|
-
ctx.lineTo(sx + 6, py + plat.height);
|
|
4740
|
-
ctx.stroke();
|
|
4741
|
-
}
|
|
4742
|
-
}
|
|
4743
|
-
if (plat.type === "goal") {
|
|
4744
|
-
ctx.fillStyle = "rgba(241, 196, 15, 0.5)";
|
|
4745
|
-
ctx.beginPath();
|
|
4746
|
-
ctx.arc(px + plat.width / 2, py - 10, 8, 0, Math.PI * 2);
|
|
4747
|
-
ctx.fill();
|
|
4748
|
-
}
|
|
4749
|
-
}
|
|
4750
|
-
const pw = resolvedPlayer.width ?? 24;
|
|
4751
|
-
const ph = resolvedPlayer.height ?? 32;
|
|
4752
|
-
const ppx = resolvedPlayer.x - camX;
|
|
4753
|
-
const ppy = resolvedPlayer.y - camY;
|
|
4754
|
-
ctx.fillStyle = PLAYER_COLOR;
|
|
4755
|
-
const radius = Math.min(pw, ph) * 0.25;
|
|
4756
|
-
ctx.beginPath();
|
|
4757
|
-
ctx.moveTo(ppx + radius, ppy);
|
|
4758
|
-
ctx.lineTo(ppx + pw - radius, ppy);
|
|
4759
|
-
ctx.quadraticCurveTo(ppx + pw, ppy, ppx + pw, ppy + radius);
|
|
4760
|
-
ctx.lineTo(ppx + pw, ppy + ph - radius);
|
|
4761
|
-
ctx.quadraticCurveTo(ppx + pw, ppy + ph, ppx + pw - radius, ppy + ph);
|
|
4762
|
-
ctx.lineTo(ppx + radius, ppy + ph);
|
|
4763
|
-
ctx.quadraticCurveTo(ppx, ppy + ph, ppx, ppy + ph - radius);
|
|
4764
|
-
ctx.lineTo(ppx, ppy + radius);
|
|
4765
|
-
ctx.quadraticCurveTo(ppx, ppy, ppx + radius, ppy);
|
|
4766
|
-
ctx.fill();
|
|
4767
|
-
const eyeY = ppy + ph * 0.3;
|
|
4768
|
-
const eyeSize = 3;
|
|
4769
|
-
const facingRight = resolvedPlayer.facingRight ?? true;
|
|
4770
|
-
const eyeOffsetX = facingRight ? pw * 0.55 : pw * 0.2;
|
|
4771
|
-
ctx.fillStyle = PLAYER_EYE_COLOR;
|
|
4772
|
-
ctx.beginPath();
|
|
4773
|
-
ctx.arc(ppx + eyeOffsetX, eyeY, eyeSize, 0, Math.PI * 2);
|
|
4774
|
-
ctx.fill();
|
|
4775
|
-
ctx.beginPath();
|
|
4776
|
-
ctx.arc(ppx + eyeOffsetX + 7, eyeY, eyeSize, 0, Math.PI * 2);
|
|
4777
|
-
ctx.fill();
|
|
4778
|
-
});
|
|
4779
|
-
return /* @__PURE__ */ jsx(
|
|
4780
|
-
"canvas",
|
|
4781
|
-
{
|
|
4782
|
-
ref: canvasRef,
|
|
4783
|
-
style: { width: canvasWidth, height: canvasHeight },
|
|
4784
|
-
className: cn("block rounded-lg border border-white/10", className),
|
|
4785
|
-
"data-testid": "platformer-canvas",
|
|
4786
|
-
tabIndex: 0
|
|
4787
|
-
}
|
|
4788
|
-
);
|
|
4789
|
-
}
|
|
4790
|
-
PlatformerCanvas.displayName = "PlatformerCanvas";
|
|
4791
3748
|
|
|
4792
3749
|
// components/organisms/game/types/effects.ts
|
|
4793
3750
|
var EMPTY_EFFECT_STATE = {
|
|
@@ -6745,717 +5702,94 @@ var PhysicsManager = class {
|
|
|
6745
5702
|
/**
|
|
6746
5703
|
* Reset all physics state
|
|
6747
5704
|
*/
|
|
6748
|
-
reset() {
|
|
6749
|
-
this.entities.clear();
|
|
6750
|
-
}
|
|
6751
|
-
};
|
|
6752
|
-
|
|
6753
|
-
// components/organisms/game/hooks/usePhysics2D.ts
|
|
6754
|
-
function usePhysics2D(options = {}) {
|
|
6755
|
-
const physicsManagerRef = useRef(null);
|
|
6756
|
-
const collisionCallbacksRef = useRef(/* @__PURE__ */ new Set());
|
|
6757
|
-
if (!physicsManagerRef.current) {
|
|
6758
|
-
physicsManagerRef.current = new PhysicsManager({
|
|
6759
|
-
gravity: options.gravity,
|
|
6760
|
-
friction: options.friction,
|
|
6761
|
-
airResistance: options.airResistance,
|
|
6762
|
-
maxVelocityY: options.maxVelocityY,
|
|
6763
|
-
groundY: options.groundY
|
|
6764
|
-
});
|
|
6765
|
-
}
|
|
6766
|
-
const manager = physicsManagerRef.current;
|
|
6767
|
-
useEffect(() => {
|
|
6768
|
-
if (options.onCollision) {
|
|
6769
|
-
collisionCallbacksRef.current.add(options.onCollision);
|
|
6770
|
-
}
|
|
6771
|
-
return () => {
|
|
6772
|
-
if (options.onCollision) {
|
|
6773
|
-
collisionCallbacksRef.current.delete(options.onCollision);
|
|
6774
|
-
}
|
|
6775
|
-
};
|
|
6776
|
-
}, [options.onCollision]);
|
|
6777
|
-
const registerUnit = useCallback((unitId, initialState = {}) => {
|
|
6778
|
-
manager.registerEntity(unitId, initialState);
|
|
6779
|
-
}, [manager]);
|
|
6780
|
-
const unregisterUnit = useCallback((unitId) => {
|
|
6781
|
-
manager.unregisterEntity(unitId);
|
|
6782
|
-
}, [manager]);
|
|
6783
|
-
const getPosition = useCallback((unitId) => {
|
|
6784
|
-
const state = manager.getState(unitId);
|
|
6785
|
-
if (!state) return null;
|
|
6786
|
-
return { x: state.x, y: state.y };
|
|
6787
|
-
}, [manager]);
|
|
6788
|
-
const getState = useCallback((unitId) => {
|
|
6789
|
-
return manager.getState(unitId);
|
|
6790
|
-
}, [manager]);
|
|
6791
|
-
const applyForce = useCallback((unitId, fx, fy) => {
|
|
6792
|
-
manager.applyForce(unitId, fx, fy);
|
|
6793
|
-
}, [manager]);
|
|
6794
|
-
const setVelocity = useCallback((unitId, vx, vy) => {
|
|
6795
|
-
manager.setVelocity(unitId, vx, vy);
|
|
6796
|
-
}, [manager]);
|
|
6797
|
-
const setPosition = useCallback((unitId, x, y) => {
|
|
6798
|
-
manager.setPosition(unitId, x, y);
|
|
6799
|
-
}, [manager]);
|
|
6800
|
-
const tick = useCallback((deltaTime = 16) => {
|
|
6801
|
-
manager.tick(deltaTime);
|
|
6802
|
-
}, [manager]);
|
|
6803
|
-
const checkCollision = useCallback((unitIdA, unitIdB, boundsA, boundsB) => {
|
|
6804
|
-
return manager.checkCollision(unitIdA, unitIdB, boundsA, boundsB);
|
|
6805
|
-
}, [manager]);
|
|
6806
|
-
const resolveCollision = useCallback((unitIdA, unitIdB) => {
|
|
6807
|
-
manager.resolveCollision(unitIdA, unitIdB);
|
|
6808
|
-
collisionCallbacksRef.current.forEach((callback) => {
|
|
6809
|
-
callback(unitIdA, unitIdB);
|
|
6810
|
-
});
|
|
6811
|
-
}, [manager]);
|
|
6812
|
-
const setFrozen = useCallback((unitId, frozen) => {
|
|
6813
|
-
manager.setFrozen(unitId, frozen);
|
|
6814
|
-
}, [manager]);
|
|
6815
|
-
const getAllUnits = useCallback(() => {
|
|
6816
|
-
return manager.getAllEntities();
|
|
6817
|
-
}, [manager]);
|
|
6818
|
-
const reset = useCallback(() => {
|
|
6819
|
-
manager.reset();
|
|
6820
|
-
}, [manager]);
|
|
6821
|
-
return {
|
|
6822
|
-
registerUnit,
|
|
6823
|
-
unregisterUnit,
|
|
6824
|
-
getPosition,
|
|
6825
|
-
getState,
|
|
6826
|
-
applyForce,
|
|
6827
|
-
setVelocity,
|
|
6828
|
-
setPosition,
|
|
6829
|
-
tick,
|
|
6830
|
-
checkCollision,
|
|
6831
|
-
resolveCollision,
|
|
6832
|
-
setFrozen,
|
|
6833
|
-
getAllUnits,
|
|
6834
|
-
reset
|
|
6835
|
-
};
|
|
6836
|
-
}
|
|
6837
|
-
var positionMap = {
|
|
6838
|
-
top: "top-0 left-0 right-0 flex justify-between items-start p-4",
|
|
6839
|
-
bottom: "bottom-0 left-0 right-0 flex justify-between items-end p-4",
|
|
6840
|
-
corners: "inset-0 pointer-events-none"
|
|
6841
|
-
};
|
|
6842
|
-
function convertElementsToStats(elements) {
|
|
6843
|
-
return elements.map((el) => {
|
|
6844
|
-
const [source, field] = el.bind?.split(".") ?? [];
|
|
6845
|
-
const labelMap = {
|
|
6846
|
-
"health-bar": "Health",
|
|
6847
|
-
"score-display": "Score",
|
|
6848
|
-
lives: "Lives",
|
|
6849
|
-
timer: "Time"
|
|
6850
|
-
};
|
|
6851
|
-
return {
|
|
6852
|
-
label: el.label || labelMap[el.type ?? ""] || el.type || "",
|
|
6853
|
-
source,
|
|
6854
|
-
field,
|
|
6855
|
-
// Pass through direct values from compiled render-ui effects
|
|
6856
|
-
...el.value !== void 0 && { value: el.value },
|
|
6857
|
-
...el.icon !== void 0 && { icon: el.icon },
|
|
6858
|
-
...el.format !== void 0 && { format: el.format },
|
|
6859
|
-
...el.max !== void 0 && { max: el.max }
|
|
6860
|
-
};
|
|
6861
|
-
});
|
|
6862
|
-
}
|
|
6863
|
-
function GameHud({
|
|
6864
|
-
position: propPosition,
|
|
6865
|
-
stats: propStats,
|
|
6866
|
-
items,
|
|
6867
|
-
elements,
|
|
6868
|
-
size = "md",
|
|
6869
|
-
className,
|
|
6870
|
-
transparent = true
|
|
6871
|
-
}) {
|
|
6872
|
-
const rawStats = propStats ?? items ?? (elements && Array.isArray(elements) ? convertElementsToStats(elements) : []);
|
|
6873
|
-
const stats = Array.isArray(rawStats) ? rawStats : [];
|
|
6874
|
-
const position = propPosition ?? "corners";
|
|
6875
|
-
if (position === "corners") {
|
|
6876
|
-
const leftStats = stats.slice(0, Math.ceil(stats.length / 2));
|
|
6877
|
-
const rightStats = stats.slice(Math.ceil(stats.length / 2));
|
|
6878
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("absolute", positionMap[position], className), children: [
|
|
6879
|
-
/* @__PURE__ */ jsx("div", { className: "absolute top-4 left-4 flex flex-col gap-2 pointer-events-auto", children: leftStats.map((stat, i) => /* @__PURE__ */ jsx(StatBadge, { ...stat, size }, i)) }),
|
|
6880
|
-
/* @__PURE__ */ jsx("div", { className: "absolute top-4 right-4 flex flex-col gap-2 items-end pointer-events-auto", children: rightStats.map((stat, i) => /* @__PURE__ */ jsx(StatBadge, { ...stat, size }, i)) })
|
|
6881
|
-
] });
|
|
6882
|
-
}
|
|
6883
|
-
return /* @__PURE__ */ jsx(
|
|
6884
|
-
"div",
|
|
6885
|
-
{
|
|
6886
|
-
className: cn(
|
|
6887
|
-
"absolute z-10",
|
|
6888
|
-
positionMap[position],
|
|
6889
|
-
transparent && "bg-gradient-to-b from-black/50 to-transparent",
|
|
6890
|
-
position === "bottom" && "bg-gradient-to-t from-black/50 to-transparent",
|
|
6891
|
-
className
|
|
6892
|
-
),
|
|
6893
|
-
children: /* @__PURE__ */ jsx("div", { className: "flex gap-4", children: stats.map((stat, i) => /* @__PURE__ */ jsx(StatBadge, { ...stat, size }, i)) })
|
|
6894
|
-
}
|
|
6895
|
-
);
|
|
6896
|
-
}
|
|
6897
|
-
GameHud.displayName = "GameHud";
|
|
6898
|
-
var variantMap = {
|
|
6899
|
-
primary: "bg-blue-600 hover:bg-blue-500 text-white border-blue-400 shadow-lg shadow-blue-500/25",
|
|
6900
|
-
secondary: "bg-gray-700 hover:bg-gray-600 text-white border-gray-500",
|
|
6901
|
-
ghost: "bg-transparent hover:bg-white/10 text-white border-white/20"
|
|
6902
|
-
};
|
|
6903
|
-
function GameMenu({
|
|
6904
|
-
title,
|
|
6905
|
-
subtitle,
|
|
6906
|
-
options,
|
|
6907
|
-
menuItems,
|
|
6908
|
-
onSelect,
|
|
6909
|
-
eventBus: eventBusProp,
|
|
6910
|
-
background,
|
|
6911
|
-
logo,
|
|
6912
|
-
className
|
|
6913
|
-
}) {
|
|
6914
|
-
const resolvedOptions = options ?? menuItems ?? [];
|
|
6915
|
-
let eventBusFromHook = null;
|
|
6916
|
-
try {
|
|
6917
|
-
eventBusFromHook = useEventBus();
|
|
6918
|
-
} catch {
|
|
6919
|
-
}
|
|
6920
|
-
const eventBus = eventBusProp || eventBusFromHook;
|
|
6921
|
-
const handleOptionClick = React.useCallback(
|
|
6922
|
-
(option) => {
|
|
6923
|
-
if (option.event && eventBus) {
|
|
6924
|
-
eventBus.emit(`UI:${option.event}`, { option });
|
|
6925
|
-
}
|
|
6926
|
-
if (onSelect) {
|
|
6927
|
-
onSelect(option);
|
|
6928
|
-
}
|
|
6929
|
-
if (option.navigatesTo && eventBus) {
|
|
6930
|
-
eventBus.emit("UI:NAVIGATE", { url: option.navigatesTo, option });
|
|
6931
|
-
}
|
|
6932
|
-
},
|
|
6933
|
-
[eventBus, onSelect]
|
|
6934
|
-
);
|
|
6935
|
-
return /* @__PURE__ */ jsxs(
|
|
6936
|
-
"div",
|
|
6937
|
-
{
|
|
6938
|
-
className: cn(
|
|
6939
|
-
"min-h-screen w-full flex flex-col items-center justify-center p-8",
|
|
6940
|
-
className
|
|
6941
|
-
),
|
|
6942
|
-
style: {
|
|
6943
|
-
background: background ?? "linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f0e17 100%)"
|
|
6944
|
-
},
|
|
6945
|
-
children: [
|
|
6946
|
-
/* @__PURE__ */ jsxs("div", { className: "text-center mb-12 animate-fade-in", children: [
|
|
6947
|
-
logo && /* @__PURE__ */ jsx(
|
|
6948
|
-
"img",
|
|
6949
|
-
{
|
|
6950
|
-
src: logo,
|
|
6951
|
-
alt: title,
|
|
6952
|
-
className: "h-24 w-auto mx-auto mb-6 drop-shadow-2xl"
|
|
6953
|
-
}
|
|
6954
|
-
),
|
|
6955
|
-
/* @__PURE__ */ jsx(
|
|
6956
|
-
"h1",
|
|
6957
|
-
{
|
|
6958
|
-
className: "text-5xl md:text-7xl font-bold text-white tracking-tight",
|
|
6959
|
-
style: {
|
|
6960
|
-
textShadow: "0 4px 12px rgba(0,0,0,0.5)"
|
|
6961
|
-
},
|
|
6962
|
-
children: title
|
|
6963
|
-
}
|
|
6964
|
-
),
|
|
6965
|
-
subtitle && /* @__PURE__ */ jsx("p", { className: "mt-2 text-lg text-gray-400 tracking-widest uppercase", children: subtitle })
|
|
6966
|
-
] }),
|
|
6967
|
-
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4 w-full max-w-md", children: resolvedOptions.map((option, index) => /* @__PURE__ */ jsx(
|
|
6968
|
-
"button",
|
|
6969
|
-
{
|
|
6970
|
-
onClick: () => handleOptionClick(option),
|
|
6971
|
-
disabled: option.disabled,
|
|
6972
|
-
className: cn(
|
|
6973
|
-
"w-full py-4 px-8 rounded-xl border-2 font-bold text-lg",
|
|
6974
|
-
"transition-all duration-200 transform",
|
|
6975
|
-
"hover:scale-105 active:scale-95",
|
|
6976
|
-
"focus:outline-none focus:ring-4 focus:ring-white/25",
|
|
6977
|
-
variantMap[option.variant ?? "secondary"] ?? variantMap.secondary,
|
|
6978
|
-
option.disabled && "opacity-50 cursor-not-allowed hover:scale-100"
|
|
6979
|
-
),
|
|
6980
|
-
style: {
|
|
6981
|
-
animationDelay: `${index * 100}ms`
|
|
6982
|
-
},
|
|
6983
|
-
children: option.label
|
|
6984
|
-
},
|
|
6985
|
-
index
|
|
6986
|
-
)) }),
|
|
6987
|
-
/* @__PURE__ */ jsxs("div", { className: "absolute inset-0 pointer-events-none overflow-hidden", children: [
|
|
6988
|
-
/* @__PURE__ */ jsx("div", { className: "absolute top-1/4 left-1/4 w-64 h-64 bg-blue-500/10 rounded-full blur-3xl" }),
|
|
6989
|
-
/* @__PURE__ */ jsx("div", { className: "absolute bottom-1/4 right-1/4 w-96 h-96 bg-purple-500/10 rounded-full blur-3xl" })
|
|
6990
|
-
] })
|
|
6991
|
-
]
|
|
6992
|
-
}
|
|
6993
|
-
);
|
|
6994
|
-
}
|
|
6995
|
-
GameMenu.displayName = "GameMenu";
|
|
6996
|
-
var variantColors = {
|
|
6997
|
-
victory: {
|
|
6998
|
-
bg: "from-green-900/90 to-emerald-950/90",
|
|
6999
|
-
title: "text-green-400",
|
|
7000
|
-
accent: "border-green-500"
|
|
7001
|
-
},
|
|
7002
|
-
defeat: {
|
|
7003
|
-
bg: "from-red-900/90 to-gray-950/90",
|
|
7004
|
-
title: "text-red-400",
|
|
7005
|
-
accent: "border-red-500"
|
|
7006
|
-
},
|
|
7007
|
-
neutral: {
|
|
7008
|
-
bg: "from-gray-900/90 to-gray-950/90",
|
|
7009
|
-
title: "text-white",
|
|
7010
|
-
accent: "border-gray-500"
|
|
7011
|
-
}
|
|
7012
|
-
};
|
|
7013
|
-
var buttonVariants = {
|
|
7014
|
-
primary: "bg-blue-600 hover:bg-blue-500 text-white border-blue-400",
|
|
7015
|
-
secondary: "bg-gray-700 hover:bg-gray-600 text-white border-gray-500",
|
|
7016
|
-
ghost: "bg-transparent hover:bg-white/10 text-white border-white/20"
|
|
7017
|
-
};
|
|
7018
|
-
function formatTime(ms) {
|
|
7019
|
-
const seconds = Math.floor(ms / 1e3);
|
|
7020
|
-
const minutes = Math.floor(seconds / 60);
|
|
7021
|
-
const remainingSeconds = seconds % 60;
|
|
7022
|
-
return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
|
|
7023
|
-
}
|
|
7024
|
-
function GameOverScreen({
|
|
7025
|
-
title,
|
|
7026
|
-
message,
|
|
7027
|
-
stats = [],
|
|
7028
|
-
actions,
|
|
7029
|
-
menuItems,
|
|
7030
|
-
onAction,
|
|
7031
|
-
eventBus: eventBusProp,
|
|
7032
|
-
variant = "neutral",
|
|
7033
|
-
highScore,
|
|
7034
|
-
currentScore,
|
|
7035
|
-
className
|
|
7036
|
-
}) {
|
|
7037
|
-
const resolvedActions = actions ?? menuItems ?? [];
|
|
7038
|
-
let eventBusFromHook = null;
|
|
7039
|
-
try {
|
|
7040
|
-
eventBusFromHook = useEventBus();
|
|
7041
|
-
} catch {
|
|
7042
|
-
}
|
|
7043
|
-
const eventBus = eventBusProp || eventBusFromHook;
|
|
7044
|
-
const handleActionClick = React.useCallback(
|
|
7045
|
-
(action) => {
|
|
7046
|
-
if (action.event && eventBus) {
|
|
7047
|
-
eventBus.emit(`UI:${action.event}`, { action });
|
|
7048
|
-
}
|
|
7049
|
-
if (onAction) {
|
|
7050
|
-
onAction(action);
|
|
7051
|
-
}
|
|
7052
|
-
},
|
|
7053
|
-
[eventBus, onAction]
|
|
7054
|
-
);
|
|
7055
|
-
const colors = variantColors[variant];
|
|
7056
|
-
const numericCurrentScore = typeof currentScore === "string" ? parseFloat(currentScore) : currentScore;
|
|
7057
|
-
const numericHighScore = typeof highScore === "string" ? parseFloat(highScore) : highScore;
|
|
7058
|
-
const isNewHighScore = numericHighScore !== void 0 && numericCurrentScore !== void 0 && !isNaN(numericCurrentScore) && !isNaN(numericHighScore) && numericCurrentScore > numericHighScore;
|
|
7059
|
-
return /* @__PURE__ */ jsxs(
|
|
7060
|
-
"div",
|
|
7061
|
-
{
|
|
7062
|
-
className: cn(
|
|
7063
|
-
"min-h-screen w-full flex flex-col items-center justify-center p-8",
|
|
7064
|
-
"bg-gradient-to-b",
|
|
7065
|
-
colors.bg,
|
|
7066
|
-
className
|
|
7067
|
-
),
|
|
7068
|
-
children: [
|
|
7069
|
-
variant === "victory" && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 pointer-events-none overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-[radial-gradient(circle_at_center,rgba(34,197,94,0.2),transparent_70%)]" }) }),
|
|
7070
|
-
/* @__PURE__ */ jsx(
|
|
7071
|
-
"h1",
|
|
7072
|
-
{
|
|
7073
|
-
className: cn(
|
|
7074
|
-
"text-6xl md:text-8xl font-bold mb-4 tracking-tight animate-bounce-once",
|
|
7075
|
-
colors.title
|
|
7076
|
-
),
|
|
7077
|
-
style: { textShadow: "0 4px 20px rgba(0,0,0,0.5)" },
|
|
7078
|
-
children: title
|
|
7079
|
-
}
|
|
7080
|
-
),
|
|
7081
|
-
message && /* @__PURE__ */ jsx("p", { className: "text-xl text-gray-300 mb-8 text-center max-w-md", children: message }),
|
|
7082
|
-
isNewHighScore && /* @__PURE__ */ jsx("div", { className: "mb-6 px-6 py-2 bg-yellow-500/20 border-2 border-yellow-500 rounded-full animate-pulse", children: /* @__PURE__ */ jsx("span", { className: "text-yellow-400 font-bold text-lg tracking-wide", children: "\u{1F3C6} NEW HIGH SCORE! \u{1F3C6}" }) }),
|
|
7083
|
-
stats.length > 0 && /* @__PURE__ */ jsx(
|
|
7084
|
-
"div",
|
|
7085
|
-
{
|
|
7086
|
-
className: cn(
|
|
7087
|
-
"mb-8 p-6 rounded-xl border-2 bg-black/30",
|
|
7088
|
-
colors.accent
|
|
7089
|
-
),
|
|
7090
|
-
children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-4", children: stats.map((stat, index) => {
|
|
7091
|
-
let displayValue = stat.value ?? 0;
|
|
7092
|
-
if (stat.format === "time" && typeof displayValue === "number") {
|
|
7093
|
-
displayValue = formatTime(displayValue);
|
|
7094
|
-
}
|
|
7095
|
-
return /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
|
|
7096
|
-
/* @__PURE__ */ jsx("div", { className: "text-gray-400 text-sm mb-1", children: stat.label }),
|
|
7097
|
-
/* @__PURE__ */ jsxs("div", { className: "text-white text-2xl font-bold flex items-center justify-center gap-2", children: [
|
|
7098
|
-
stat.icon && /* @__PURE__ */ jsx("span", { children: stat.icon }),
|
|
7099
|
-
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: displayValue })
|
|
7100
|
-
] })
|
|
7101
|
-
] }, index);
|
|
7102
|
-
}) })
|
|
7103
|
-
}
|
|
7104
|
-
),
|
|
7105
|
-
/* @__PURE__ */ jsx("div", { className: "flex flex-col sm:flex-row gap-4", children: resolvedActions.map((action, index) => /* @__PURE__ */ jsx(
|
|
7106
|
-
"button",
|
|
7107
|
-
{
|
|
7108
|
-
onClick: () => handleActionClick(action),
|
|
7109
|
-
className: cn(
|
|
7110
|
-
"px-8 py-4 rounded-xl border-2 font-bold text-lg",
|
|
7111
|
-
"transition-all duration-200",
|
|
7112
|
-
"hover:scale-105 active:scale-95",
|
|
7113
|
-
"focus:outline-none focus:ring-4 focus:ring-white/25",
|
|
7114
|
-
buttonVariants[action.variant ?? "secondary"]
|
|
7115
|
-
),
|
|
7116
|
-
children: action.label
|
|
7117
|
-
},
|
|
7118
|
-
index
|
|
7119
|
-
)) })
|
|
7120
|
-
]
|
|
7121
|
-
}
|
|
7122
|
-
);
|
|
7123
|
-
}
|
|
7124
|
-
GameOverScreen.displayName = "GameOverScreen";
|
|
7125
|
-
function InventoryPanel({
|
|
7126
|
-
items,
|
|
7127
|
-
slots,
|
|
7128
|
-
columns,
|
|
7129
|
-
selectedSlot,
|
|
7130
|
-
onSelectSlot,
|
|
7131
|
-
onUseItem,
|
|
7132
|
-
onDropItem,
|
|
7133
|
-
selectSlotEvent,
|
|
7134
|
-
useItemEvent,
|
|
7135
|
-
dropItemEvent,
|
|
7136
|
-
showTooltips = true,
|
|
7137
|
-
className,
|
|
7138
|
-
slotSize = 48
|
|
7139
|
-
}) {
|
|
7140
|
-
const eventBus = useEventBus();
|
|
7141
|
-
const [hoveredSlot, setHoveredSlot] = useState(null);
|
|
7142
|
-
const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
|
|
7143
|
-
const safeItems = Array.isArray(items) ? items : [];
|
|
7144
|
-
const safeSlots = typeof slots === "number" && slots > 0 ? slots : 0;
|
|
7145
|
-
const safeColumns = typeof columns === "number" && columns > 0 ? columns : 1;
|
|
7146
|
-
const slotArray = Array.from({ length: safeSlots }, (_, index) => {
|
|
7147
|
-
return safeItems[index] ?? null;
|
|
7148
|
-
});
|
|
7149
|
-
const rows = Math.ceil(safeSlots / safeColumns);
|
|
7150
|
-
const handleSlotClick = useCallback((index) => {
|
|
7151
|
-
if (selectSlotEvent) eventBus.emit(`UI:${selectSlotEvent}`, { index });
|
|
7152
|
-
onSelectSlot?.(index);
|
|
7153
|
-
}, [onSelectSlot, selectSlotEvent, eventBus]);
|
|
7154
|
-
const handleSlotDoubleClick = useCallback((index) => {
|
|
7155
|
-
const item = slotArray[index];
|
|
7156
|
-
if (item) {
|
|
7157
|
-
if (useItemEvent) eventBus.emit(`UI:${useItemEvent}`, { item });
|
|
7158
|
-
onUseItem?.(item);
|
|
7159
|
-
}
|
|
7160
|
-
}, [slotArray, onUseItem, useItemEvent, eventBus]);
|
|
7161
|
-
const handleKeyDown = useCallback((e, index) => {
|
|
7162
|
-
const item = slotArray[index];
|
|
7163
|
-
switch (e.key) {
|
|
7164
|
-
case "Enter":
|
|
7165
|
-
case " ":
|
|
7166
|
-
if (item) {
|
|
7167
|
-
e.preventDefault();
|
|
7168
|
-
if (useItemEvent) eventBus.emit(`UI:${useItemEvent}`, { item });
|
|
7169
|
-
onUseItem?.(item);
|
|
7170
|
-
}
|
|
7171
|
-
break;
|
|
7172
|
-
case "Delete":
|
|
7173
|
-
case "Backspace":
|
|
7174
|
-
if (item) {
|
|
7175
|
-
e.preventDefault();
|
|
7176
|
-
if (dropItemEvent) eventBus.emit(`UI:${dropItemEvent}`, { item });
|
|
7177
|
-
onDropItem?.(item);
|
|
7178
|
-
}
|
|
7179
|
-
break;
|
|
7180
|
-
case "ArrowRight":
|
|
7181
|
-
e.preventDefault();
|
|
7182
|
-
onSelectSlot?.(Math.min(index + 1, safeSlots - 1));
|
|
7183
|
-
break;
|
|
7184
|
-
case "ArrowLeft":
|
|
7185
|
-
e.preventDefault();
|
|
7186
|
-
onSelectSlot?.(Math.max(index - 1, 0));
|
|
7187
|
-
break;
|
|
7188
|
-
case "ArrowDown":
|
|
7189
|
-
e.preventDefault();
|
|
7190
|
-
onSelectSlot?.(Math.min(index + safeColumns, safeSlots - 1));
|
|
7191
|
-
break;
|
|
7192
|
-
case "ArrowUp":
|
|
7193
|
-
e.preventDefault();
|
|
7194
|
-
onSelectSlot?.(Math.max(index - safeColumns, 0));
|
|
7195
|
-
break;
|
|
7196
|
-
}
|
|
7197
|
-
}, [slotArray, onUseItem, onDropItem, onSelectSlot, safeColumns, safeSlots, useItemEvent, dropItemEvent, eventBus]);
|
|
7198
|
-
const handleMouseEnter = useCallback((e, index) => {
|
|
7199
|
-
if (showTooltips && slotArray[index]) {
|
|
7200
|
-
setHoveredSlot(index);
|
|
7201
|
-
setTooltipPosition({
|
|
7202
|
-
x: e.clientX + 10,
|
|
7203
|
-
y: e.clientY + 10
|
|
7204
|
-
});
|
|
7205
|
-
}
|
|
7206
|
-
}, [showTooltips, slotArray]);
|
|
7207
|
-
const handleMouseLeave = useCallback(() => {
|
|
7208
|
-
setHoveredSlot(null);
|
|
7209
|
-
}, []);
|
|
7210
|
-
const hoveredItem = hoveredSlot !== null ? slotArray[hoveredSlot] : null;
|
|
7211
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
|
|
7212
|
-
/* @__PURE__ */ jsx(
|
|
7213
|
-
"div",
|
|
7214
|
-
{
|
|
7215
|
-
className: "grid gap-1 bg-gray-900 p-2 rounded-lg border border-gray-700",
|
|
7216
|
-
style: {
|
|
7217
|
-
gridTemplateColumns: `repeat(${safeColumns}, ${slotSize}px)`,
|
|
7218
|
-
gridTemplateRows: `repeat(${rows}, ${slotSize}px)`
|
|
7219
|
-
},
|
|
7220
|
-
children: slotArray.map((item, index) => /* @__PURE__ */ jsx(
|
|
7221
|
-
"button",
|
|
7222
|
-
{
|
|
7223
|
-
type: "button",
|
|
7224
|
-
className: cn(
|
|
7225
|
-
"relative flex items-center justify-center",
|
|
7226
|
-
"bg-gray-800 border rounded transition-colors",
|
|
7227
|
-
"hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500",
|
|
7228
|
-
selectedSlot === index ? "border-yellow-400 bg-gray-700" : "border-gray-600"
|
|
7229
|
-
),
|
|
7230
|
-
style: { width: slotSize, height: slotSize },
|
|
7231
|
-
onClick: () => handleSlotClick(index),
|
|
7232
|
-
onDoubleClick: () => handleSlotDoubleClick(index),
|
|
7233
|
-
onKeyDown: (e) => handleKeyDown(e, index),
|
|
7234
|
-
onMouseEnter: (e) => handleMouseEnter(e, index),
|
|
7235
|
-
onMouseLeave: handleMouseLeave,
|
|
7236
|
-
tabIndex: 0,
|
|
7237
|
-
"aria-label": item ? `${item.name || item.type}, quantity: ${item.quantity}` : `Empty slot ${index + 1}`,
|
|
7238
|
-
children: item && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
7239
|
-
item.sprite ? /* @__PURE__ */ jsx(
|
|
7240
|
-
"img",
|
|
7241
|
-
{
|
|
7242
|
-
src: item.sprite,
|
|
7243
|
-
alt: item.name || item.type,
|
|
7244
|
-
className: "w-8 h-8 object-contain",
|
|
7245
|
-
style: { imageRendering: "pixelated" }
|
|
7246
|
-
}
|
|
7247
|
-
) : /* @__PURE__ */ jsx("div", { className: "w-8 h-8 bg-gray-600 rounded flex items-center justify-center text-xs text-gray-300", children: item.type.charAt(0).toUpperCase() }),
|
|
7248
|
-
item.quantity > 1 && /* @__PURE__ */ jsx("span", { className: "absolute bottom-0 right-0 bg-black bg-opacity-70 text-white text-xs px-1 rounded-tl", children: item.quantity })
|
|
7249
|
-
] })
|
|
7250
|
-
},
|
|
7251
|
-
index
|
|
7252
|
-
))
|
|
7253
|
-
}
|
|
7254
|
-
),
|
|
7255
|
-
showTooltips && hoveredItem && /* @__PURE__ */ jsxs(
|
|
7256
|
-
"div",
|
|
7257
|
-
{
|
|
7258
|
-
className: "fixed z-50 bg-gray-900 border border-gray-600 rounded-lg p-2 shadow-lg pointer-events-none",
|
|
7259
|
-
style: {
|
|
7260
|
-
left: tooltipPosition.x,
|
|
7261
|
-
top: tooltipPosition.y,
|
|
7262
|
-
maxWidth: 200
|
|
7263
|
-
},
|
|
7264
|
-
children: [
|
|
7265
|
-
/* @__PURE__ */ jsx("div", { className: "font-semibold text-white", children: hoveredItem.name || hoveredItem.type }),
|
|
7266
|
-
hoveredItem.description && /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-400 mt-1", children: hoveredItem.description }),
|
|
7267
|
-
/* @__PURE__ */ jsxs("div", { className: "text-xs text-gray-500 mt-1", children: [
|
|
7268
|
-
"Quantity: ",
|
|
7269
|
-
hoveredItem.quantity
|
|
7270
|
-
] })
|
|
7271
|
-
]
|
|
7272
|
-
}
|
|
7273
|
-
)
|
|
7274
|
-
] });
|
|
7275
|
-
}
|
|
7276
|
-
function DialogueBox({
|
|
7277
|
-
dialogue,
|
|
7278
|
-
typewriterSpeed = 30,
|
|
7279
|
-
position = "bottom",
|
|
7280
|
-
onComplete,
|
|
7281
|
-
onChoice,
|
|
7282
|
-
onAdvance,
|
|
7283
|
-
completeEvent,
|
|
7284
|
-
choiceEvent,
|
|
7285
|
-
advanceEvent,
|
|
7286
|
-
className
|
|
7287
|
-
}) {
|
|
7288
|
-
const eventBus = useEventBus();
|
|
7289
|
-
const [displayedText, setDisplayedText] = useState("");
|
|
7290
|
-
const [isTyping, setIsTyping] = useState(false);
|
|
7291
|
-
const [selectedChoice, setSelectedChoice] = useState(0);
|
|
7292
|
-
const textRef = useRef(dialogue.text);
|
|
7293
|
-
const charIndexRef = useRef(0);
|
|
7294
|
-
const autoAdvanceTimerRef = useRef(null);
|
|
5705
|
+
reset() {
|
|
5706
|
+
this.entities.clear();
|
|
5707
|
+
}
|
|
5708
|
+
};
|
|
5709
|
+
|
|
5710
|
+
// components/organisms/game/hooks/usePhysics2D.ts
|
|
5711
|
+
function usePhysics2D(options = {}) {
|
|
5712
|
+
const physicsManagerRef = useRef(null);
|
|
5713
|
+
const collisionCallbacksRef = useRef(/* @__PURE__ */ new Set());
|
|
5714
|
+
if (!physicsManagerRef.current) {
|
|
5715
|
+
physicsManagerRef.current = new PhysicsManager({
|
|
5716
|
+
gravity: options.gravity,
|
|
5717
|
+
friction: options.friction,
|
|
5718
|
+
airResistance: options.airResistance,
|
|
5719
|
+
maxVelocityY: options.maxVelocityY,
|
|
5720
|
+
groundY: options.groundY
|
|
5721
|
+
});
|
|
5722
|
+
}
|
|
5723
|
+
const manager = physicsManagerRef.current;
|
|
7295
5724
|
useEffect(() => {
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
setDisplayedText("");
|
|
7299
|
-
setSelectedChoice(0);
|
|
7300
|
-
if (typewriterSpeed === 0) {
|
|
7301
|
-
setDisplayedText(dialogue.text);
|
|
7302
|
-
setIsTyping(false);
|
|
7303
|
-
if (completeEvent) eventBus.emit(`UI:${completeEvent}`, {});
|
|
7304
|
-
onComplete?.();
|
|
7305
|
-
} else {
|
|
7306
|
-
setIsTyping(true);
|
|
5725
|
+
if (options.onCollision) {
|
|
5726
|
+
collisionCallbacksRef.current.add(options.onCollision);
|
|
7307
5727
|
}
|
|
7308
5728
|
return () => {
|
|
7309
|
-
if (
|
|
7310
|
-
|
|
5729
|
+
if (options.onCollision) {
|
|
5730
|
+
collisionCallbacksRef.current.delete(options.onCollision);
|
|
7311
5731
|
}
|
|
7312
5732
|
};
|
|
7313
|
-
}, [
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
}
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
}, [
|
|
7352
|
-
const
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
e.preventDefault();
|
|
7374
|
-
const choice = enabledChoices2[selectedChoice];
|
|
7375
|
-
if (choice) {
|
|
7376
|
-
if (choiceEvent) eventBus.emit(`UI:${choiceEvent}`, { choice });
|
|
7377
|
-
onChoice?.(choice);
|
|
7378
|
-
}
|
|
7379
|
-
break;
|
|
7380
|
-
case "1":
|
|
7381
|
-
case "2":
|
|
7382
|
-
case "3":
|
|
7383
|
-
case "4":
|
|
7384
|
-
const choiceIndex = parseInt(e.key) - 1;
|
|
7385
|
-
if (choiceIndex < enabledChoices2.length) {
|
|
7386
|
-
e.preventDefault();
|
|
7387
|
-
if (choiceEvent) eventBus.emit(`UI:${choiceEvent}`, { choice: enabledChoices2[choiceIndex] });
|
|
7388
|
-
onChoice?.(enabledChoices2[choiceIndex]);
|
|
7389
|
-
}
|
|
7390
|
-
break;
|
|
7391
|
-
}
|
|
7392
|
-
} else {
|
|
7393
|
-
if (e.key === " " || e.key === "Enter") {
|
|
7394
|
-
e.preventDefault();
|
|
7395
|
-
if (advanceEvent) eventBus.emit(`UI:${advanceEvent}`, {});
|
|
7396
|
-
onAdvance?.();
|
|
7397
|
-
}
|
|
7398
|
-
}
|
|
7399
|
-
}, [isTyping, skipTypewriter, dialogue.choices, selectedChoice, onChoice, onAdvance, choiceEvent, advanceEvent, eventBus]);
|
|
7400
|
-
const enabledChoices = dialogue.choices?.filter((c) => !c.disabled) ?? [];
|
|
7401
|
-
return /* @__PURE__ */ jsx(
|
|
7402
|
-
"div",
|
|
7403
|
-
{
|
|
7404
|
-
className: cn(
|
|
7405
|
-
"fixed left-0 right-0 z-40",
|
|
7406
|
-
position === "top" ? "top-0" : "bottom-0",
|
|
7407
|
-
className
|
|
7408
|
-
),
|
|
7409
|
-
onClick: handleClick,
|
|
7410
|
-
onKeyDown: handleKeyDown,
|
|
7411
|
-
tabIndex: 0,
|
|
7412
|
-
role: "dialog",
|
|
7413
|
-
"aria-label": "Dialogue",
|
|
7414
|
-
children: /* @__PURE__ */ jsx("div", { className: "mx-4 my-4 bg-gray-900 bg-opacity-95 border-2 border-gray-600 rounded-lg overflow-hidden", children: /* @__PURE__ */ jsxs("div", { className: "flex", children: [
|
|
7415
|
-
dialogue.portrait && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 p-4 border-r border-gray-700", children: /* @__PURE__ */ jsx(
|
|
7416
|
-
"img",
|
|
7417
|
-
{
|
|
7418
|
-
src: dialogue.portrait,
|
|
7419
|
-
alt: dialogue.speaker,
|
|
7420
|
-
className: "w-24 h-24 object-contain",
|
|
7421
|
-
style: { imageRendering: "pixelated" }
|
|
7422
|
-
}
|
|
7423
|
-
) }),
|
|
7424
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1 p-4", children: [
|
|
7425
|
-
/* @__PURE__ */ jsx("div", { className: "text-yellow-400 font-bold mb-2", children: dialogue.speaker }),
|
|
7426
|
-
/* @__PURE__ */ jsxs("div", { className: "text-white text-lg leading-relaxed min-h-[60px]", children: [
|
|
7427
|
-
displayedText,
|
|
7428
|
-
isTyping && /* @__PURE__ */ jsx("span", { className: "animate-pulse", children: "\u258C" })
|
|
7429
|
-
] }),
|
|
7430
|
-
!isTyping && enabledChoices.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-4 space-y-2", children: enabledChoices.map((choice, index) => /* @__PURE__ */ jsxs(
|
|
7431
|
-
"button",
|
|
7432
|
-
{
|
|
7433
|
-
type: "button",
|
|
7434
|
-
className: cn(
|
|
7435
|
-
"block w-full text-left px-4 py-2 rounded transition-colors",
|
|
7436
|
-
"hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-yellow-400",
|
|
7437
|
-
selectedChoice === index ? "bg-gray-700 text-yellow-400" : "bg-gray-800 text-white"
|
|
7438
|
-
),
|
|
7439
|
-
onClick: (e) => {
|
|
7440
|
-
e.stopPropagation();
|
|
7441
|
-
if (choiceEvent) eventBus.emit(`UI:${choiceEvent}`, { choice });
|
|
7442
|
-
onChoice?.(choice);
|
|
7443
|
-
},
|
|
7444
|
-
children: [
|
|
7445
|
-
/* @__PURE__ */ jsxs("span", { className: "text-gray-500 mr-2", children: [
|
|
7446
|
-
index + 1,
|
|
7447
|
-
"."
|
|
7448
|
-
] }),
|
|
7449
|
-
choice.text
|
|
7450
|
-
]
|
|
7451
|
-
},
|
|
7452
|
-
index
|
|
7453
|
-
)) }),
|
|
7454
|
-
!isTyping && !dialogue.choices?.length && /* @__PURE__ */ jsx("div", { className: "mt-4 text-gray-500 text-sm animate-pulse", children: "Press SPACE or click to continue..." })
|
|
7455
|
-
] })
|
|
7456
|
-
] }) })
|
|
7457
|
-
}
|
|
7458
|
-
);
|
|
5733
|
+
}, [options.onCollision]);
|
|
5734
|
+
const registerUnit = useCallback((unitId, initialState = {}) => {
|
|
5735
|
+
manager.registerEntity(unitId, initialState);
|
|
5736
|
+
}, [manager]);
|
|
5737
|
+
const unregisterUnit = useCallback((unitId) => {
|
|
5738
|
+
manager.unregisterEntity(unitId);
|
|
5739
|
+
}, [manager]);
|
|
5740
|
+
const getPosition = useCallback((unitId) => {
|
|
5741
|
+
const state = manager.getState(unitId);
|
|
5742
|
+
if (!state) return null;
|
|
5743
|
+
return { x: state.x, y: state.y };
|
|
5744
|
+
}, [manager]);
|
|
5745
|
+
const getState = useCallback((unitId) => {
|
|
5746
|
+
return manager.getState(unitId);
|
|
5747
|
+
}, [manager]);
|
|
5748
|
+
const applyForce = useCallback((unitId, fx, fy) => {
|
|
5749
|
+
manager.applyForce(unitId, fx, fy);
|
|
5750
|
+
}, [manager]);
|
|
5751
|
+
const setVelocity = useCallback((unitId, vx, vy) => {
|
|
5752
|
+
manager.setVelocity(unitId, vx, vy);
|
|
5753
|
+
}, [manager]);
|
|
5754
|
+
const setPosition = useCallback((unitId, x, y) => {
|
|
5755
|
+
manager.setPosition(unitId, x, y);
|
|
5756
|
+
}, [manager]);
|
|
5757
|
+
const tick = useCallback((deltaTime = 16) => {
|
|
5758
|
+
manager.tick(deltaTime);
|
|
5759
|
+
}, [manager]);
|
|
5760
|
+
const checkCollision = useCallback((unitIdA, unitIdB, boundsA, boundsB) => {
|
|
5761
|
+
return manager.checkCollision(unitIdA, unitIdB, boundsA, boundsB);
|
|
5762
|
+
}, [manager]);
|
|
5763
|
+
const resolveCollision = useCallback((unitIdA, unitIdB) => {
|
|
5764
|
+
manager.resolveCollision(unitIdA, unitIdB);
|
|
5765
|
+
collisionCallbacksRef.current.forEach((callback) => {
|
|
5766
|
+
callback(unitIdA, unitIdB);
|
|
5767
|
+
});
|
|
5768
|
+
}, [manager]);
|
|
5769
|
+
const setFrozen = useCallback((unitId, frozen) => {
|
|
5770
|
+
manager.setFrozen(unitId, frozen);
|
|
5771
|
+
}, [manager]);
|
|
5772
|
+
const getAllUnits = useCallback(() => {
|
|
5773
|
+
return manager.getAllEntities();
|
|
5774
|
+
}, [manager]);
|
|
5775
|
+
const reset = useCallback(() => {
|
|
5776
|
+
manager.reset();
|
|
5777
|
+
}, [manager]);
|
|
5778
|
+
return {
|
|
5779
|
+
registerUnit,
|
|
5780
|
+
unregisterUnit,
|
|
5781
|
+
getPosition,
|
|
5782
|
+
getState,
|
|
5783
|
+
applyForce,
|
|
5784
|
+
setVelocity,
|
|
5785
|
+
setPosition,
|
|
5786
|
+
tick,
|
|
5787
|
+
checkCollision,
|
|
5788
|
+
resolveCollision,
|
|
5789
|
+
setFrozen,
|
|
5790
|
+
getAllUnits,
|
|
5791
|
+
reset
|
|
5792
|
+
};
|
|
7459
5793
|
}
|
|
7460
5794
|
function BattleBoard({
|
|
7461
5795
|
entity,
|
|
@@ -8316,7 +6650,7 @@ function LinearView({
|
|
|
8316
6650
|
/* @__PURE__ */ jsx(HStack, { className: "flex-wrap items-center", gap: "xs", children: trait.states.map((state, i) => {
|
|
8317
6651
|
const isDone = i < currentIdx;
|
|
8318
6652
|
const isCurrent = i === currentIdx;
|
|
8319
|
-
return /* @__PURE__ */ jsxs(
|
|
6653
|
+
return /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
8320
6654
|
i > 0 && /* @__PURE__ */ jsx(
|
|
8321
6655
|
Typography,
|
|
8322
6656
|
{
|
|
@@ -8970,7 +7304,7 @@ function SequenceBar({
|
|
|
8970
7304
|
onSlotRemove(index);
|
|
8971
7305
|
}, [onSlotRemove, playing]);
|
|
8972
7306
|
const paddedSlots = Array.from({ length: maxSlots }, (_, i) => slots[i]);
|
|
8973
|
-
return /* @__PURE__ */ jsx(HStack, { className: cn("items-center", className), gap: "sm", children: paddedSlots.map((slot, i) => /* @__PURE__ */ jsxs(
|
|
7307
|
+
return /* @__PURE__ */ jsx(HStack, { className: cn("items-center", className), gap: "sm", children: paddedSlots.map((slot, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
8974
7308
|
i > 0 && /* @__PURE__ */ jsx(
|
|
8975
7309
|
Typography,
|
|
8976
7310
|
{
|
|
@@ -10841,20 +9175,27 @@ function SimulationCanvas({
|
|
|
10841
9175
|
bodiesRef.current = structuredClone(preset.bodies);
|
|
10842
9176
|
}, [preset]);
|
|
10843
9177
|
const step = useCallback(() => {
|
|
10844
|
-
const dt = 1 / 60 * speed;
|
|
9178
|
+
const dt = Math.min(1 / 60 * speed, 1 / 15);
|
|
10845
9179
|
const gx = preset.gravity?.x ?? 0;
|
|
10846
9180
|
const gy = preset.gravity?.y ?? 9.81;
|
|
10847
9181
|
const bodies = bodiesRef.current;
|
|
9182
|
+
const maxVel = Math.max(width, height) * 5;
|
|
10848
9183
|
for (const body of bodies) {
|
|
10849
9184
|
if (body.fixed) continue;
|
|
10850
9185
|
body.vx += gx * dt;
|
|
10851
9186
|
body.vy += gy * dt;
|
|
9187
|
+
body.vx = Math.max(-maxVel, Math.min(maxVel, body.vx));
|
|
9188
|
+
body.vy = Math.max(-maxVel, Math.min(maxVel, body.vy));
|
|
10852
9189
|
body.x += body.vx * dt;
|
|
10853
9190
|
body.y += body.vy * dt;
|
|
10854
9191
|
if (body.y + body.radius > height) {
|
|
10855
9192
|
body.y = height - body.radius;
|
|
10856
9193
|
body.vy = -body.vy * 0.8;
|
|
10857
9194
|
}
|
|
9195
|
+
if (body.y - body.radius < 0) {
|
|
9196
|
+
body.y = body.radius;
|
|
9197
|
+
body.vy = -body.vy * 0.8;
|
|
9198
|
+
}
|
|
10858
9199
|
if (body.x + body.radius > width) {
|
|
10859
9200
|
body.x = width - body.radius;
|
|
10860
9201
|
body.vx = -body.vx * 0.8;
|
|
@@ -10940,7 +9281,24 @@ function SimulationCanvas({
|
|
|
10940
9281
|
useEffect(() => {
|
|
10941
9282
|
draw();
|
|
10942
9283
|
}, [draw]);
|
|
10943
|
-
|
|
9284
|
+
useEffect(() => {
|
|
9285
|
+
if (typeof window === "undefined") return;
|
|
9286
|
+
const canvas = canvasRef.current;
|
|
9287
|
+
if (!canvas) return;
|
|
9288
|
+
bindCanvasCapture(() => canvas.toDataURL("image/png"));
|
|
9289
|
+
return () => {
|
|
9290
|
+
bindCanvasCapture(() => null);
|
|
9291
|
+
};
|
|
9292
|
+
}, []);
|
|
9293
|
+
return /* @__PURE__ */ jsx(Box, { className: cn("flex justify-center", className), children: /* @__PURE__ */ jsx(
|
|
9294
|
+
"canvas",
|
|
9295
|
+
{
|
|
9296
|
+
ref: canvasRef,
|
|
9297
|
+
width,
|
|
9298
|
+
height,
|
|
9299
|
+
className: "rounded-md block max-w-full h-auto"
|
|
9300
|
+
}
|
|
9301
|
+
) });
|
|
10944
9302
|
}
|
|
10945
9303
|
SimulationCanvas.displayName = "SimulationCanvas";
|
|
10946
9304
|
function SimulationControls({
|
|
@@ -11060,84 +9418,6 @@ function SimulationGraph({
|
|
|
11060
9418
|
] }) });
|
|
11061
9419
|
}
|
|
11062
9420
|
SimulationGraph.displayName = "SimulationGraph";
|
|
11063
|
-
var eventIcons = {
|
|
11064
|
-
attack: Sword,
|
|
11065
|
-
defend: Shield,
|
|
11066
|
-
heal: Heart,
|
|
11067
|
-
move: Move,
|
|
11068
|
-
special: Zap,
|
|
11069
|
-
death: Sword,
|
|
11070
|
-
spawn: Zap
|
|
11071
|
-
};
|
|
11072
|
-
var eventColors = {
|
|
11073
|
-
attack: "text-error",
|
|
11074
|
-
defend: "text-info",
|
|
11075
|
-
heal: "text-success",
|
|
11076
|
-
move: "text-primary",
|
|
11077
|
-
special: "text-warning",
|
|
11078
|
-
death: "text-muted-foreground",
|
|
11079
|
-
spawn: "text-accent"
|
|
11080
|
-
};
|
|
11081
|
-
var eventBadgeVariants = {
|
|
11082
|
-
attack: "danger",
|
|
11083
|
-
defend: "primary",
|
|
11084
|
-
heal: "success",
|
|
11085
|
-
move: "warning",
|
|
11086
|
-
special: "secondary",
|
|
11087
|
-
death: "secondary",
|
|
11088
|
-
spawn: "secondary"
|
|
11089
|
-
};
|
|
11090
|
-
function CombatLog({
|
|
11091
|
-
events,
|
|
11092
|
-
maxVisible = 50,
|
|
11093
|
-
autoScroll = true,
|
|
11094
|
-
showTimestamps = false,
|
|
11095
|
-
className,
|
|
11096
|
-
title = "Combat Log"
|
|
11097
|
-
}) {
|
|
11098
|
-
const scrollRef = useRef(null);
|
|
11099
|
-
useEffect(() => {
|
|
11100
|
-
if (autoScroll && scrollRef.current) {
|
|
11101
|
-
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
11102
|
-
}
|
|
11103
|
-
}, [events, autoScroll]);
|
|
11104
|
-
const visibleEvents = events.slice(-maxVisible);
|
|
11105
|
-
return /* @__PURE__ */ jsxs(Card, { variant: "default", className: cn("flex flex-col", className), children: [
|
|
11106
|
-
/* @__PURE__ */ jsx(Box, { padding: "sm", border: true, className: "border-b-2 border-x-0 border-t-0 border-[var(--color-border)]", children: /* @__PURE__ */ jsxs(Box, { display: "flex", className: "items-center justify-between", children: [
|
|
11107
|
-
/* @__PURE__ */ jsx(Typography, { variant: "body2", className: "font-bold", children: title }),
|
|
11108
|
-
/* @__PURE__ */ jsxs(Badge, { variant: "neutral", size: "sm", children: [
|
|
11109
|
-
events.length,
|
|
11110
|
-
" events"
|
|
11111
|
-
] })
|
|
11112
|
-
] }) }),
|
|
11113
|
-
/* @__PURE__ */ jsx(Box, { ref: scrollRef, overflow: "auto", className: "flex-1 max-h-64", children: visibleEvents.length === 0 ? /* @__PURE__ */ jsx(Box, { padding: "md", className: "text-center opacity-50", children: /* @__PURE__ */ jsx(Typography, { variant: "body2", children: "No events yet" }) }) : /* @__PURE__ */ jsx(Box, { padding: "xs", className: "space-y-1", children: visibleEvents.map((event) => {
|
|
11114
|
-
const EventIcon = eventIcons[event.type];
|
|
11115
|
-
const colorClass = eventColors[event.type];
|
|
11116
|
-
return /* @__PURE__ */ jsxs(
|
|
11117
|
-
Box,
|
|
11118
|
-
{
|
|
11119
|
-
display: "flex",
|
|
11120
|
-
padding: "xs",
|
|
11121
|
-
rounded: "sm",
|
|
11122
|
-
className: cn("items-start gap-2 hover:bg-[var(--color-muted)] transition-colors", event.type === "death" && "opacity-60"),
|
|
11123
|
-
children: [
|
|
11124
|
-
/* @__PURE__ */ jsx(Box, { className: cn("flex-shrink-0 mt-0.5", colorClass), children: /* @__PURE__ */ jsx(EventIcon, { className: "h-4 w-4" }) }),
|
|
11125
|
-
/* @__PURE__ */ jsxs(Box, { className: "flex-1 min-w-0", children: [
|
|
11126
|
-
/* @__PURE__ */ jsx(Typography, { variant: "caption", className: "block", children: event.message }),
|
|
11127
|
-
event.value !== void 0 && /* @__PURE__ */ jsxs(Badge, { variant: eventBadgeVariants[event.type], size: "sm", className: "mt-1", children: [
|
|
11128
|
-
event.type === "heal" ? "+" : event.type === "attack" ? "-" : "",
|
|
11129
|
-
event.value
|
|
11130
|
-
] })
|
|
11131
|
-
] }),
|
|
11132
|
-
(event.turn || showTimestamps) && /* @__PURE__ */ jsx(Box, { className: "flex-shrink-0", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "opacity-40", children: event.turn ? `T${event.turn}` : "" }) })
|
|
11133
|
-
]
|
|
11134
|
-
},
|
|
11135
|
-
event.id
|
|
11136
|
-
);
|
|
11137
|
-
}) }) })
|
|
11138
|
-
] });
|
|
11139
|
-
}
|
|
11140
|
-
CombatLog.displayName = "CombatLog";
|
|
11141
9421
|
|
|
11142
9422
|
// components/organisms/game/types/game.ts
|
|
11143
9423
|
function createInitialGameState(width, height, units, defaultTerrain = "floorStone") {
|
|
@@ -11278,7 +9558,7 @@ function generateCombatMessage(event) {
|
|
|
11278
9558
|
return event.message;
|
|
11279
9559
|
}
|
|
11280
9560
|
function extractTitle(children) {
|
|
11281
|
-
if (!
|
|
9561
|
+
if (!React.isValidElement(children)) return void 0;
|
|
11282
9562
|
const props = children.props;
|
|
11283
9563
|
if (typeof props.title === "string") {
|
|
11284
9564
|
return props.title;
|
|
@@ -11313,7 +9593,7 @@ var ModalSlot = ({
|
|
|
11313
9593
|
};
|
|
11314
9594
|
ModalSlot.displayName = "ModalSlot";
|
|
11315
9595
|
function extractTitle2(children) {
|
|
11316
|
-
if (!
|
|
9596
|
+
if (!React.isValidElement(children)) return void 0;
|
|
11317
9597
|
const props = children.props;
|
|
11318
9598
|
if (typeof props.title === "string") {
|
|
11319
9599
|
return props.title;
|
|
@@ -11350,7 +9630,7 @@ var DrawerSlot = ({
|
|
|
11350
9630
|
};
|
|
11351
9631
|
DrawerSlot.displayName = "DrawerSlot";
|
|
11352
9632
|
function extractToastProps(children) {
|
|
11353
|
-
if (!
|
|
9633
|
+
if (!React.isValidElement(children)) {
|
|
11354
9634
|
if (typeof children === "string") {
|
|
11355
9635
|
return { message: children };
|
|
11356
9636
|
}
|
|
@@ -11381,7 +9661,7 @@ var ToastSlot = ({
|
|
|
11381
9661
|
eventBus.emit("UI:CLOSE");
|
|
11382
9662
|
};
|
|
11383
9663
|
if (!isVisible) return null;
|
|
11384
|
-
const isCustomContent =
|
|
9664
|
+
const isCustomContent = React.isValidElement(children) && !message;
|
|
11385
9665
|
return /* @__PURE__ */ jsx(Box, { className: "fixed bottom-4 right-4 z-50", children: isCustomContent ? children : /* @__PURE__ */ jsx(
|
|
11386
9666
|
Toast,
|
|
11387
9667
|
{
|
|
@@ -11698,209 +9978,6 @@ var Chart = ({
|
|
|
11698
9978
|
] }) });
|
|
11699
9979
|
};
|
|
11700
9980
|
Chart.displayName = "Chart";
|
|
11701
|
-
var DEFAULT_THRESHOLDS = [
|
|
11702
|
-
{ value: 30, color: "var(--color-error)" },
|
|
11703
|
-
{ value: 70, color: "var(--color-warning)" },
|
|
11704
|
-
{ value: 100, color: "var(--color-success)" }
|
|
11705
|
-
];
|
|
11706
|
-
function getColorForValue(value, max, thresholds) {
|
|
11707
|
-
const percentage = value / max * 100;
|
|
11708
|
-
for (const threshold of thresholds) {
|
|
11709
|
-
if (percentage <= threshold.value) {
|
|
11710
|
-
return threshold.color;
|
|
11711
|
-
}
|
|
11712
|
-
}
|
|
11713
|
-
return thresholds[thresholds.length - 1]?.color ?? "var(--color-primary)";
|
|
11714
|
-
}
|
|
11715
|
-
var radialSizes = {
|
|
11716
|
-
sm: { size: 80, stroke: 6, fontSize: "12px" },
|
|
11717
|
-
md: { size: 120, stroke: 8, fontSize: "16px" },
|
|
11718
|
-
lg: { size: 160, stroke: 10, fontSize: "20px" }
|
|
11719
|
-
};
|
|
11720
|
-
var Meter = ({
|
|
11721
|
-
value,
|
|
11722
|
-
min = 0,
|
|
11723
|
-
max = 100,
|
|
11724
|
-
label,
|
|
11725
|
-
unit,
|
|
11726
|
-
variant = "linear",
|
|
11727
|
-
thresholds = DEFAULT_THRESHOLDS,
|
|
11728
|
-
segments = 5,
|
|
11729
|
-
showValue = true,
|
|
11730
|
-
size = "md",
|
|
11731
|
-
actions,
|
|
11732
|
-
entity,
|
|
11733
|
-
isLoading = false,
|
|
11734
|
-
error,
|
|
11735
|
-
className
|
|
11736
|
-
}) => {
|
|
11737
|
-
const eventBus = useEventBus();
|
|
11738
|
-
const handleAction = useCallback(
|
|
11739
|
-
(action) => {
|
|
11740
|
-
if (action.event) {
|
|
11741
|
-
eventBus.emit(`UI:${action.event}`, { value });
|
|
11742
|
-
}
|
|
11743
|
-
},
|
|
11744
|
-
[eventBus, value]
|
|
11745
|
-
);
|
|
11746
|
-
const percentage = useMemo(() => {
|
|
11747
|
-
const range = max - min;
|
|
11748
|
-
if (range <= 0) return 0;
|
|
11749
|
-
return Math.min(Math.max((value - min) / range * 100, 0), 100);
|
|
11750
|
-
}, [value, min, max]);
|
|
11751
|
-
const activeColor = useMemo(
|
|
11752
|
-
() => getColorForValue(value, max, thresholds),
|
|
11753
|
-
[value, max, thresholds]
|
|
11754
|
-
);
|
|
11755
|
-
const displayValue = useMemo(() => {
|
|
11756
|
-
const formatted = Number.isInteger(value) ? value : value.toFixed(1);
|
|
11757
|
-
return unit ? `${formatted}${unit}` : `${formatted}`;
|
|
11758
|
-
}, [value, unit]);
|
|
11759
|
-
if (isLoading) {
|
|
11760
|
-
return /* @__PURE__ */ jsx(LoadingState, { message: "Loading meter...", className });
|
|
11761
|
-
}
|
|
11762
|
-
if (error) {
|
|
11763
|
-
return /* @__PURE__ */ jsx(
|
|
11764
|
-
ErrorState,
|
|
11765
|
-
{
|
|
11766
|
-
title: "Meter error",
|
|
11767
|
-
message: error.message,
|
|
11768
|
-
className
|
|
11769
|
-
}
|
|
11770
|
-
);
|
|
11771
|
-
}
|
|
11772
|
-
if (variant === "radial") {
|
|
11773
|
-
const dims = radialSizes[size];
|
|
11774
|
-
const radius = (dims.size - dims.stroke * 2) / 2;
|
|
11775
|
-
const circumference = 2 * Math.PI * radius;
|
|
11776
|
-
const offset = circumference - percentage / 100 * circumference;
|
|
11777
|
-
const center = dims.size / 2;
|
|
11778
|
-
return /* @__PURE__ */ jsx(Card, { className: cn("p-4", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", align: "center", children: [
|
|
11779
|
-
label && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", weight: "medium", children: label }),
|
|
11780
|
-
/* @__PURE__ */ jsxs(Box, { className: "relative inline-flex items-center justify-center", children: [
|
|
11781
|
-
/* @__PURE__ */ jsxs(
|
|
11782
|
-
"svg",
|
|
11783
|
-
{
|
|
11784
|
-
width: dims.size,
|
|
11785
|
-
height: dims.size,
|
|
11786
|
-
viewBox: `0 0 ${dims.size} ${dims.size}`,
|
|
11787
|
-
className: "transform -rotate-90",
|
|
11788
|
-
children: [
|
|
11789
|
-
/* @__PURE__ */ jsx(
|
|
11790
|
-
"circle",
|
|
11791
|
-
{
|
|
11792
|
-
cx: center,
|
|
11793
|
-
cy: center,
|
|
11794
|
-
r: radius,
|
|
11795
|
-
fill: "none",
|
|
11796
|
-
stroke: "var(--color-muted)",
|
|
11797
|
-
strokeWidth: dims.stroke
|
|
11798
|
-
}
|
|
11799
|
-
),
|
|
11800
|
-
/* @__PURE__ */ jsx(
|
|
11801
|
-
"circle",
|
|
11802
|
-
{
|
|
11803
|
-
cx: center,
|
|
11804
|
-
cy: center,
|
|
11805
|
-
r: radius,
|
|
11806
|
-
fill: "none",
|
|
11807
|
-
stroke: activeColor,
|
|
11808
|
-
strokeWidth: dims.stroke,
|
|
11809
|
-
strokeDasharray: circumference,
|
|
11810
|
-
strokeDashoffset: offset,
|
|
11811
|
-
strokeLinecap: "round",
|
|
11812
|
-
className: "transition-all duration-500 ease-out"
|
|
11813
|
-
}
|
|
11814
|
-
)
|
|
11815
|
-
]
|
|
11816
|
-
}
|
|
11817
|
-
),
|
|
11818
|
-
showValue && /* @__PURE__ */ jsx(Box, { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx(
|
|
11819
|
-
Typography,
|
|
11820
|
-
{
|
|
11821
|
-
variant: "h5",
|
|
11822
|
-
weight: "bold",
|
|
11823
|
-
className: "tabular-nums",
|
|
11824
|
-
style: { fontSize: dims.fontSize },
|
|
11825
|
-
children: displayValue
|
|
11826
|
-
}
|
|
11827
|
-
) })
|
|
11828
|
-
] }),
|
|
11829
|
-
actions && actions.length > 0 && /* @__PURE__ */ jsx(HStack, { gap: "xs", children: actions.map((action, idx) => /* @__PURE__ */ jsx(
|
|
11830
|
-
Badge,
|
|
11831
|
-
{
|
|
11832
|
-
variant: "default",
|
|
11833
|
-
className: "cursor-pointer hover:opacity-80 transition-opacity",
|
|
11834
|
-
onClick: () => handleAction(action),
|
|
11835
|
-
children: action.label
|
|
11836
|
-
},
|
|
11837
|
-
idx
|
|
11838
|
-
)) })
|
|
11839
|
-
] }) });
|
|
11840
|
-
}
|
|
11841
|
-
if (variant === "segmented") {
|
|
11842
|
-
const activeSegments = Math.round(percentage / 100 * segments);
|
|
11843
|
-
return /* @__PURE__ */ jsx(Card, { className: cn("p-4", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
11844
|
-
(label || showValue) && /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "center", children: [
|
|
11845
|
-
label && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", weight: "medium", children: label }),
|
|
11846
|
-
showValue && /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "tabular-nums", children: displayValue })
|
|
11847
|
-
] }),
|
|
11848
|
-
/* @__PURE__ */ jsx(HStack, { gap: "xs", className: "w-full", children: Array.from({ length: segments }).map((_, idx) => {
|
|
11849
|
-
const isActive = idx < activeSegments;
|
|
11850
|
-
const segColor = isActive ? getColorForValue((idx + 1) / segments * max, max, thresholds) : void 0;
|
|
11851
|
-
return /* @__PURE__ */ jsx(
|
|
11852
|
-
Box,
|
|
11853
|
-
{
|
|
11854
|
-
className: cn(
|
|
11855
|
-
"h-2 flex-1 rounded-[var(--radius-sm)] transition-all duration-300",
|
|
11856
|
-
!isActive && "bg-[var(--color-muted)]"
|
|
11857
|
-
),
|
|
11858
|
-
style: isActive ? { backgroundColor: segColor } : void 0
|
|
11859
|
-
},
|
|
11860
|
-
idx
|
|
11861
|
-
);
|
|
11862
|
-
}) }),
|
|
11863
|
-
thresholds.some((t) => t.label) && /* @__PURE__ */ jsx(HStack, { justify: "between", className: "w-full", children: thresholds.map((t, idx) => /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", children: t.label || "" }, idx)) })
|
|
11864
|
-
] }) });
|
|
11865
|
-
}
|
|
11866
|
-
return /* @__PURE__ */ jsx(Card, { className: cn("p-4", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
|
|
11867
|
-
(label || showValue) && /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "center", children: [
|
|
11868
|
-
label && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", weight: "medium", children: label }),
|
|
11869
|
-
showValue && /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "bold", className: "tabular-nums", children: displayValue })
|
|
11870
|
-
] }),
|
|
11871
|
-
/* @__PURE__ */ jsx(Box, { className: "w-full h-3 bg-[var(--color-muted)] rounded-[var(--radius-full)] overflow-hidden", children: /* @__PURE__ */ jsx(
|
|
11872
|
-
Box,
|
|
11873
|
-
{
|
|
11874
|
-
className: "h-full rounded-[var(--radius-full)] transition-all duration-500 ease-out",
|
|
11875
|
-
style: {
|
|
11876
|
-
width: `${percentage}%`,
|
|
11877
|
-
backgroundColor: activeColor
|
|
11878
|
-
}
|
|
11879
|
-
}
|
|
11880
|
-
) }),
|
|
11881
|
-
/* @__PURE__ */ jsxs(HStack, { justify: "between", className: "w-full", children: [
|
|
11882
|
-
/* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", children: [
|
|
11883
|
-
min,
|
|
11884
|
-
unit ? ` ${unit}` : ""
|
|
11885
|
-
] }),
|
|
11886
|
-
/* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", children: [
|
|
11887
|
-
max,
|
|
11888
|
-
unit ? ` ${unit}` : ""
|
|
11889
|
-
] })
|
|
11890
|
-
] }),
|
|
11891
|
-
actions && actions.length > 0 && /* @__PURE__ */ jsx(HStack, { gap: "xs", children: actions.map((action, idx) => /* @__PURE__ */ jsx(
|
|
11892
|
-
Badge,
|
|
11893
|
-
{
|
|
11894
|
-
variant: "default",
|
|
11895
|
-
className: "cursor-pointer hover:opacity-80 transition-opacity",
|
|
11896
|
-
onClick: () => handleAction(action),
|
|
11897
|
-
children: action.label
|
|
11898
|
-
},
|
|
11899
|
-
idx
|
|
11900
|
-
)) })
|
|
11901
|
-
] }) });
|
|
11902
|
-
};
|
|
11903
|
-
Meter.displayName = "Meter";
|
|
11904
9981
|
var STATUS_STYLES3 = {
|
|
11905
9982
|
complete: {
|
|
11906
9983
|
dotColor: "text-[var(--color-success)]",
|
|
@@ -11935,7 +10012,7 @@ var Timeline = ({
|
|
|
11935
10012
|
}) => {
|
|
11936
10013
|
const { t } = useTranslate();
|
|
11937
10014
|
const entityData = Array.isArray(entity) ? entity : [];
|
|
11938
|
-
const items =
|
|
10015
|
+
const items = React.useMemo(() => {
|
|
11939
10016
|
if (propItems) return propItems;
|
|
11940
10017
|
if (entityData.length === 0) return [];
|
|
11941
10018
|
return entityData.map((record, idx) => {
|
|
@@ -12076,7 +10153,7 @@ var MediaGallery = ({
|
|
|
12076
10153
|
[selectable, selectedItems, selectionEvent, eventBus]
|
|
12077
10154
|
);
|
|
12078
10155
|
const entityData = Array.isArray(entity) ? entity : [];
|
|
12079
|
-
const items =
|
|
10156
|
+
const items = React.useMemo(() => {
|
|
12080
10157
|
if (propItems) return propItems;
|
|
12081
10158
|
if (entityData.length === 0) return [];
|
|
12082
10159
|
return entityData.map((record, idx) => ({
|
|
@@ -12647,7 +10724,7 @@ var GraphCanvas = ({
|
|
|
12647
10724
|
const handleNodeClick = useCallback(
|
|
12648
10725
|
(node) => {
|
|
12649
10726
|
if (nodeClickEvent) {
|
|
12650
|
-
eventBus.emit(`UI:${nodeClickEvent}`, { row: node });
|
|
10727
|
+
eventBus.emit(`UI:${nodeClickEvent}`, { id: node.id, row: node });
|
|
12651
10728
|
}
|
|
12652
10729
|
onNodeClick?.(node);
|
|
12653
10730
|
},
|
|
@@ -14064,4 +12141,4 @@ function WorldMapTemplate({
|
|
|
14064
12141
|
}
|
|
14065
12142
|
WorldMapTemplate.displayName = "WorldMapTemplate";
|
|
14066
12143
|
|
|
14067
|
-
export { ALL_PRESETS, AR_BOOK_FIELDS, ActionPalette, ActionTile, AuthLayout, BattleBoard, BattleTemplate, BookChapterView, BookCoverPage, BookNavBar, BookTableOfContents, BookViewer, BuilderBoard, CanvasEffect, CastleBoard, CastleTemplate, Chart, ClassifierBoard, CodeView, CodeViewer, CollapsibleSection,
|
|
12144
|
+
export { ALL_PRESETS, AR_BOOK_FIELDS, ActionPalette, ActionTile, AuthLayout, BattleBoard, BattleTemplate, BookChapterView, BookCoverPage, BookNavBar, BookTableOfContents, BookViewer, BuilderBoard, CanvasEffect, CastleBoard, CastleTemplate, Chart, ClassifierBoard, CodeView, CodeViewer, CollapsibleSection, ConfirmDialog, ContentRenderer, CounterTemplate, DashboardGrid, DashboardLayout, DebuggerBoard, DocumentViewer, StateMachineView as DomStateMachineVisualizer, DrawerSlot, EditorCheckbox, EditorSelect, EditorSlider, EditorTextInput, EditorToolbar, EventHandlerBoard, EventLog, FEATURE_TYPES, FormActions, FormLayout, FormSection, GameAudioContext, GameAudioProvider, GameAudioToggle, GameShell, GameTemplate, GenericAppTemplate, GraphCanvas, Header, IDENTITY_BOOK_FIELDS, JazariStateMachine, List, MediaGallery, ModalSlot, Navigation, NegotiatorBoard, NotifyListener, ObjectRulePanel, StateMachineView as OrbitalStateMachineView, OrbitalVisualization, PhysicsManager, RuleEditor, SHEET_COLUMNS, SPRITE_SHEET_LAYOUT, Section, SequenceBar, SequencerBoard, Sidebar, SignaturePad, SimulationCanvas, SimulationControls, SimulationGraph, SimulatorBoard, Split, SplitPane, StateArchitectBoard, StateMachineView, StateNode2 as StateNode, StatusBar, TERRAIN_COLORS, TabbedContainer, Table, TerrainPalette, Timeline, ToastSlot, TraitSlot, TraitStateViewer, TransitionArrow, UncontrolledBattleBoard, VariablePanel, WizardContainer, WorldMapBoard, WorldMapTemplate, applyTemporaryEffect, calculateAttackTargets, calculateDamage, calculateValidMoves, combatAnimations, combatClasses, combatEffects, createInitialGameState, createUnitAnimationState, generateCombatMessage, getCurrentFrame, inferDirection, mapBookData, pendulum, projectileMotion, resolveFieldMap, resolveFrame, resolveSheetDirection, springOscillator, tickAnimationState, transitionAnimation, useBattleState, useGameAudio, useGameAudioContext, usePhysics2D, useSpriteAnimations };
|