@almadar/ui 5.71.0 → 5.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/avl/index.cjs +1780 -1951
- package/dist/avl/index.js +513 -684
- package/dist/components/game/atoms/ScoreDisplay.d.ts +1 -3
- package/dist/components/game/atoms/Sprite.d.ts +6 -1
- package/dist/components/game/atoms/XPBar.d.ts +1 -1
- package/dist/components/game/molecules/GameCanvas3D.d.ts +0 -2
- package/dist/components/game/molecules/IsometricCanvas.d.ts +4 -2
- package/dist/components/game/molecules/PlatformerCanvas.d.ts +4 -1
- package/dist/components/game/molecules/StatBadge.d.ts +3 -1
- package/dist/components/game/molecules/three/index.cjs +4 -81
- package/dist/components/game/molecules/three/index.js +4 -81
- package/dist/components/game/molecules/useUnitSpriteAtlas.d.ts +3 -2
- package/dist/components/game/organisms/types/isometric.d.ts +16 -0
- package/dist/components/index.cjs +1906 -2057
- package/dist/components/index.js +794 -945
- package/dist/providers/index.cjs +1589 -1760
- package/dist/providers/index.js +489 -660
- package/dist/runtime/index.cjs +1645 -1816
- package/dist/runtime/index.js +493 -664
- package/package.json +1 -1
|
@@ -15,12 +15,10 @@ export interface ScoreDisplayProps {
|
|
|
15
15
|
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
16
16
|
/** Additional CSS classes */
|
|
17
17
|
className?: string;
|
|
18
|
-
/** Animation on value change */
|
|
19
|
-
animated?: boolean;
|
|
20
18
|
/** Number formatting locale */
|
|
21
19
|
locale?: string;
|
|
22
20
|
}
|
|
23
|
-
export declare function ScoreDisplay({ assetUrl, value, score, label, icon, size, className,
|
|
21
|
+
export declare function ScoreDisplay({ assetUrl, value, score, label, icon, size, className, locale, }: ScoreDisplayProps): import("react/jsx-runtime").JSX.Element;
|
|
24
22
|
export declare namespace ScoreDisplay {
|
|
25
23
|
var displayName: string;
|
|
26
24
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import type { EventKey, AssetUrl } from "@almadar/core";
|
|
8
|
+
import type { AnimationName } from '../organisms/types/spriteAnimation';
|
|
8
9
|
export interface SpriteProps {
|
|
9
10
|
/** Spritesheet image URL */
|
|
10
11
|
spritesheet: AssetUrl;
|
|
@@ -32,6 +33,10 @@ export interface SpriteProps {
|
|
|
32
33
|
zIndex?: number;
|
|
33
34
|
/** Number of columns in spritesheet (for frame calculation) */
|
|
34
35
|
columns?: number;
|
|
36
|
+
/** Animation state name — drives lolo-ui config knob for animation selection */
|
|
37
|
+
animState?: AnimationName;
|
|
38
|
+
/** Frames per second — drives lolo-ui config knob for animation speed */
|
|
39
|
+
frameRate?: number;
|
|
35
40
|
/** Optional className */
|
|
36
41
|
className?: string;
|
|
37
42
|
/** Optional onClick handler */
|
|
@@ -56,7 +61,7 @@ export interface SpriteProps {
|
|
|
56
61
|
* />
|
|
57
62
|
* ```
|
|
58
63
|
*/
|
|
59
|
-
export declare function Sprite({ spritesheet, frameWidth, frameHeight, frame, x, y, scale, flipX, flipY, rotation, opacity, zIndex, columns, className, onClick, action, }: SpriteProps): React.JSX.Element;
|
|
64
|
+
export declare function Sprite({ spritesheet, frameWidth, frameHeight, frame, x, y, scale, flipX, flipY, rotation, opacity, zIndex, columns, animState: _animState, frameRate: _frameRate, className, onClick, action, }: SpriteProps): React.JSX.Element;
|
|
60
65
|
/**
|
|
61
66
|
* Canvas-based sprite renderer for better performance in game loops
|
|
62
67
|
*/
|
|
@@ -9,7 +9,7 @@ export interface XPBarProps {
|
|
|
9
9
|
showLabel?: boolean;
|
|
10
10
|
/** Size variant */
|
|
11
11
|
size?: 'sm' | 'md' | 'lg';
|
|
12
|
-
/** Animate the fill bar */
|
|
12
|
+
/** Animate the fill bar with a CSS transition */
|
|
13
13
|
animated?: boolean;
|
|
14
14
|
/** Additional CSS classes */
|
|
15
15
|
className?: string;
|
|
@@ -46,8 +46,6 @@ export interface GameCanvas3DProps {
|
|
|
46
46
|
isLoading?: boolean;
|
|
47
47
|
/** Error state */
|
|
48
48
|
error?: string | null;
|
|
49
|
-
/** Entity name for schema-driven auto-fetch */
|
|
50
|
-
entity?: string;
|
|
51
49
|
/** Array of tiles to render */
|
|
52
50
|
tiles?: IsometricTile[];
|
|
53
51
|
/** Array of units to render */
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import * as React from 'react';
|
|
27
27
|
import type { AssetUrl, EventEmit } from '@almadar/core';
|
|
28
|
-
import type { IsometricTile, IsometricUnit, IsometricFeature } from '../organisms/types/isometric';
|
|
28
|
+
import type { IsometricTile, IsometricUnit, IsometricFeature, ActiveEffect } from '../organisms/types/isometric';
|
|
29
29
|
import type { ResolvedFrame } from '../organisms/types/spriteAnimation';
|
|
30
30
|
import type { TileLayout } from '../organisms/utils/isometric';
|
|
31
31
|
import type { UiError } from '../../core/atoms/types';
|
|
@@ -52,6 +52,8 @@ export interface IsometricCanvasProps {
|
|
|
52
52
|
units?: IsometricUnit[];
|
|
53
53
|
/** Array of features (resources, portals, buildings, etc.) */
|
|
54
54
|
features?: IsometricFeature[];
|
|
55
|
+
/** Active visual effects to draw after units. Sprite URLs resolved via assetManifest.effects[fx.key]. */
|
|
56
|
+
effects?: ActiveEffect[];
|
|
55
57
|
/** Currently selected unit ID */
|
|
56
58
|
selectedUnitId?: string | null;
|
|
57
59
|
/** Valid move positions (shown as pulsing green highlights) */
|
|
@@ -132,7 +134,7 @@ export interface IsometricCanvasProps {
|
|
|
132
134
|
effects?: Record<string, string>;
|
|
133
135
|
};
|
|
134
136
|
}
|
|
135
|
-
export declare function IsometricCanvas({ className, isLoading, error, tiles: _tilesPropRaw, units: _unitsPropRaw, features: _featuresPropRaw, selectedUnitId, validMoves, attackTargets, hoveredTile, onTileClick, onUnitClick, onTileHover, onTileLeave, tileClickEvent, unitClickEvent, tileHoverEvent, tileLeaveEvent, tileLayout, scale, debug, backgroundImage, showMinimap, enableCamera, unitScale, spriteHeightRatio, spriteMaxWidthRatio, getTerrainSprite, getFeatureSprite, getUnitSprite, resolveUnitFrame, effectSpriteUrls, onDrawEffects, hasActiveEffects, diamondTopY: diamondTopYProp, assetBaseUrl, assetManifest, }: IsometricCanvasProps): React.JSX.Element;
|
|
137
|
+
export declare function IsometricCanvas({ className, isLoading, error, tiles: _tilesPropRaw, units: _unitsPropRaw, features: _featuresPropRaw, effects: _effectsPropRaw, selectedUnitId, validMoves, attackTargets, hoveredTile, onTileClick, onUnitClick, onTileHover, onTileLeave, tileClickEvent, unitClickEvent, tileHoverEvent, tileLeaveEvent, tileLayout, scale, debug, backgroundImage, showMinimap, enableCamera, unitScale, spriteHeightRatio, spriteMaxWidthRatio, getTerrainSprite, getFeatureSprite, getUnitSprite, resolveUnitFrame, effectSpriteUrls, onDrawEffects, hasActiveEffects: _hasActiveEffects, diamondTopY: diamondTopYProp, assetBaseUrl, assetManifest, }: IsometricCanvasProps): React.JSX.Element;
|
|
136
138
|
export declare namespace IsometricCanvas {
|
|
137
139
|
var displayName: string;
|
|
138
140
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AssetUrl, EventEmit } from '@almadar/core';
|
|
2
|
+
import type { ActiveEffect } from '../organisms/types/isometric';
|
|
2
3
|
export interface PlatformerPlatform {
|
|
3
4
|
x: number;
|
|
4
5
|
y: number;
|
|
@@ -48,10 +49,12 @@ export interface PlatformerCanvasProps {
|
|
|
48
49
|
}>;
|
|
49
50
|
jumpEvent?: EventEmit<Record<string, never>>;
|
|
50
51
|
stopEvent?: EventEmit<Record<string, never>>;
|
|
52
|
+
/** Active visual effects (key, world x/y, ttl) — drawn after the player */
|
|
53
|
+
effects?: ActiveEffect[];
|
|
51
54
|
/** Additional CSS classes */
|
|
52
55
|
className?: string;
|
|
53
56
|
}
|
|
54
|
-
export declare function PlatformerCanvas({ player, platforms, worldWidth, worldHeight, canvasWidth, canvasHeight, followCamera, bgColor, playerSprite, tileSprites, backgroundImage, assetBaseUrl, leftEvent, rightEvent, jumpEvent, stopEvent, className, }: PlatformerCanvasProps): import("react/jsx-runtime").JSX.Element;
|
|
57
|
+
export declare function PlatformerCanvas({ player, platforms, worldWidth, worldHeight, canvasWidth, canvasHeight, followCamera, bgColor, playerSprite, tileSprites, backgroundImage, assetBaseUrl, leftEvent, rightEvent, jumpEvent, stopEvent, effects, className, }: PlatformerCanvasProps): import("react/jsx-runtime").JSX.Element;
|
|
55
58
|
export declare namespace PlatformerCanvas {
|
|
56
59
|
var displayName: string;
|
|
57
60
|
}
|
|
@@ -9,6 +9,8 @@ import type { AssetUrl } from '@almadar/core';
|
|
|
9
9
|
export interface StatBadgeProps {
|
|
10
10
|
/** Sprite image URL — takes precedence over icon when provided */
|
|
11
11
|
assetUrl?: AssetUrl;
|
|
12
|
+
/** Asset-image icon URL (board ui/ PNG); takes precedence over the Lucide icon. */
|
|
13
|
+
iconUrl?: AssetUrl;
|
|
12
14
|
/** Stat label */
|
|
13
15
|
label: string;
|
|
14
16
|
/** Current value (defaults to 0 if not provided) */
|
|
@@ -30,7 +32,7 @@ export interface StatBadgeProps {
|
|
|
30
32
|
/** Additional CSS classes */
|
|
31
33
|
className?: string;
|
|
32
34
|
}
|
|
33
|
-
export declare function StatBadge({ assetUrl, label, value, max, format, icon, size, variant, className, source: _source, field: _field, }: StatBadgeProps): import("react/jsx-runtime").JSX.Element;
|
|
35
|
+
export declare function StatBadge({ assetUrl, iconUrl, label, value, max, format, icon, size, variant, className, source: _source, field: _field, }: StatBadgeProps): import("react/jsx-runtime").JSX.Element;
|
|
34
36
|
export declare namespace StatBadge {
|
|
35
37
|
var displayName: string;
|
|
36
38
|
}
|
|
@@ -2293,13 +2293,6 @@ function preloadFeatures(urls) {
|
|
|
2293
2293
|
}
|
|
2294
2294
|
|
|
2295
2295
|
// components/game/organisms/utils/spriteAnimation.ts
|
|
2296
|
-
function inferDirection(dx, dy) {
|
|
2297
|
-
if (dx === 0 && dy === 0) return "se";
|
|
2298
|
-
if (dx >= 0 && dy >= 0) return "se";
|
|
2299
|
-
if (dx <= 0 && dy >= 0) return "sw";
|
|
2300
|
-
if (dx >= 0 && dy <= 0) return "ne";
|
|
2301
|
-
return "nw";
|
|
2302
|
-
}
|
|
2303
2296
|
function resolveSheetDirection(facing) {
|
|
2304
2297
|
switch (facing) {
|
|
2305
2298
|
case "se":
|
|
@@ -2320,22 +2313,8 @@ function frameRect(frame, row, columns, frameWidth, frameHeight) {
|
|
|
2320
2313
|
sh: frameHeight
|
|
2321
2314
|
};
|
|
2322
2315
|
}
|
|
2323
|
-
function getCurrentFrameFromDef(def, elapsed) {
|
|
2324
|
-
const frameDuration = 1e3 / def.frameRate;
|
|
2325
|
-
const totalDuration = def.frames * frameDuration;
|
|
2326
|
-
if (def.loop) {
|
|
2327
|
-
const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
|
|
2328
|
-
return { frame: frame2, finished: false };
|
|
2329
|
-
}
|
|
2330
|
-
if (elapsed >= totalDuration) {
|
|
2331
|
-
return { frame: def.frames - 1, finished: true };
|
|
2332
|
-
}
|
|
2333
|
-
const frame = Math.floor(elapsed / frameDuration);
|
|
2334
|
-
return { frame, finished: false };
|
|
2335
|
-
}
|
|
2336
2316
|
|
|
2337
2317
|
// components/game/molecules/useUnitSpriteAtlas.ts
|
|
2338
|
-
var WALK_HOLD_MS = 600;
|
|
2339
2318
|
function unitAtlasUrl(unit) {
|
|
2340
2319
|
if (unit.spriteSheet) return unit.spriteSheet;
|
|
2341
2320
|
const sprite = unit.sprite;
|
|
@@ -2357,9 +2336,6 @@ function useUnitSpriteAtlas(units) {
|
|
|
2357
2336
|
const loadingRef = React11.useRef(/* @__PURE__ */ new Set());
|
|
2358
2337
|
const [pendingCount, setPendingCount] = React11.useState(0);
|
|
2359
2338
|
const [, forceTick] = React11.useState(0);
|
|
2360
|
-
const animStatesRef = React11.useRef(/* @__PURE__ */ new Map());
|
|
2361
|
-
const lastTickRef = React11.useRef(0);
|
|
2362
|
-
const rafRef = React11.useRef(0);
|
|
2363
2339
|
const atlasUrls = React11.useMemo(() => {
|
|
2364
2340
|
const set = /* @__PURE__ */ new Set();
|
|
2365
2341
|
for (const unit of units) {
|
|
@@ -2405,54 +2381,6 @@ function useUnitSpriteAtlas(units) {
|
|
|
2405
2381
|
}
|
|
2406
2382
|
return [...urls];
|
|
2407
2383
|
}, [units, pendingCount]);
|
|
2408
|
-
React11.useEffect(() => {
|
|
2409
|
-
const hasAtlasUnits = units.some((u) => unitAtlasUrl(u) !== null);
|
|
2410
|
-
if (!hasAtlasUnits) return;
|
|
2411
|
-
let running = true;
|
|
2412
|
-
const tick = (ts) => {
|
|
2413
|
-
if (!running) return;
|
|
2414
|
-
const last = lastTickRef.current || ts;
|
|
2415
|
-
const delta = ts - last;
|
|
2416
|
-
lastTickRef.current = ts;
|
|
2417
|
-
const states = animStatesRef.current;
|
|
2418
|
-
const currentIds = /* @__PURE__ */ new Set();
|
|
2419
|
-
for (const unit of units) {
|
|
2420
|
-
if (unitAtlasUrl(unit) === null) continue;
|
|
2421
|
-
currentIds.add(unit.id);
|
|
2422
|
-
let state = states.get(unit.id);
|
|
2423
|
-
if (!state) {
|
|
2424
|
-
state = { animation: "idle", direction: "se", elapsed: 0, walkHold: 0, prev: null };
|
|
2425
|
-
states.set(unit.id, state);
|
|
2426
|
-
}
|
|
2427
|
-
const posX = unit.position?.x ?? unit.x ?? 0;
|
|
2428
|
-
const posY = unit.position?.y ?? unit.y ?? 0;
|
|
2429
|
-
if (state.prev) {
|
|
2430
|
-
const dx = posX - state.prev.x;
|
|
2431
|
-
const dy = posY - state.prev.y;
|
|
2432
|
-
if (dx !== 0 || dy !== 0) {
|
|
2433
|
-
state.animation = "walk";
|
|
2434
|
-
state.direction = inferDirection(dx, dy);
|
|
2435
|
-
state.walkHold = WALK_HOLD_MS;
|
|
2436
|
-
} else if (state.animation === "walk") {
|
|
2437
|
-
state.walkHold -= delta;
|
|
2438
|
-
if (state.walkHold <= 0) state.animation = "idle";
|
|
2439
|
-
}
|
|
2440
|
-
}
|
|
2441
|
-
state.prev = { x: posX, y: posY };
|
|
2442
|
-
state.elapsed += delta;
|
|
2443
|
-
}
|
|
2444
|
-
for (const id of states.keys()) {
|
|
2445
|
-
if (!currentIds.has(id)) states.delete(id);
|
|
2446
|
-
}
|
|
2447
|
-
rafRef.current = requestAnimationFrame(tick);
|
|
2448
|
-
};
|
|
2449
|
-
rafRef.current = requestAnimationFrame(tick);
|
|
2450
|
-
return () => {
|
|
2451
|
-
running = false;
|
|
2452
|
-
cancelAnimationFrame(rafRef.current);
|
|
2453
|
-
lastTickRef.current = 0;
|
|
2454
|
-
};
|
|
2455
|
-
}, [units]);
|
|
2456
2384
|
const resolveUnitFrame = React11.useCallback((unitId) => {
|
|
2457
2385
|
const unit = units.find((u) => u.id === unitId);
|
|
2458
2386
|
if (!unit) return null;
|
|
@@ -2460,18 +2388,14 @@ function useUnitSpriteAtlas(units) {
|
|
|
2460
2388
|
if (!atlasUrl) return null;
|
|
2461
2389
|
const atlas = atlasCacheRef.current.get(atlasUrl);
|
|
2462
2390
|
if (!atlas) return null;
|
|
2463
|
-
const
|
|
2464
|
-
const
|
|
2465
|
-
const direction = state?.direction ?? "se";
|
|
2466
|
-
const elapsed = state?.elapsed ?? 0;
|
|
2391
|
+
const animation = unit.animation ?? "idle";
|
|
2392
|
+
const frame = unit.frame ?? 0;
|
|
2467
2393
|
const def = atlas.animations[animation] ?? atlas.animations.idle;
|
|
2468
2394
|
if (!def) return null;
|
|
2469
|
-
const { sheetDir, flipX } = resolveSheetDirection(
|
|
2395
|
+
const { sheetDir, flipX } = resolveSheetDirection("se");
|
|
2470
2396
|
const rel = atlas.sheets[sheetDir] ?? atlas.sheets.se ?? atlas.sheets.sw;
|
|
2471
2397
|
if (!rel) return null;
|
|
2472
2398
|
const sheetUrl = resolveSheetUrl(atlasUrl, rel);
|
|
2473
|
-
const isIdle = animation === "idle";
|
|
2474
|
-
const frame = isIdle ? 0 : getCurrentFrameFromDef(def, elapsed).frame;
|
|
2475
2399
|
const rect = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
|
|
2476
2400
|
return {
|
|
2477
2401
|
sheetUrl,
|
|
@@ -2480,7 +2404,7 @@ function useUnitSpriteAtlas(units) {
|
|
|
2480
2404
|
sw: rect.sw,
|
|
2481
2405
|
sh: rect.sh,
|
|
2482
2406
|
flipX,
|
|
2483
|
-
applyBreathing:
|
|
2407
|
+
applyBreathing: animation === "idle"
|
|
2484
2408
|
};
|
|
2485
2409
|
}, [units]);
|
|
2486
2410
|
return { sheetUrls, resolveUnitFrame, pendingCount };
|
|
@@ -2576,7 +2500,6 @@ var GameCanvas3D = React11.forwardRef(
|
|
|
2576
2500
|
className,
|
|
2577
2501
|
isLoading: externalLoading,
|
|
2578
2502
|
error: externalError,
|
|
2579
|
-
entity,
|
|
2580
2503
|
preloadAssets = [],
|
|
2581
2504
|
tileClickEvent,
|
|
2582
2505
|
unitClickEvent,
|
|
@@ -2269,13 +2269,6 @@ function preloadFeatures(urls) {
|
|
|
2269
2269
|
}
|
|
2270
2270
|
|
|
2271
2271
|
// components/game/organisms/utils/spriteAnimation.ts
|
|
2272
|
-
function inferDirection(dx, dy) {
|
|
2273
|
-
if (dx === 0 && dy === 0) return "se";
|
|
2274
|
-
if (dx >= 0 && dy >= 0) return "se";
|
|
2275
|
-
if (dx <= 0 && dy >= 0) return "sw";
|
|
2276
|
-
if (dx >= 0 && dy <= 0) return "ne";
|
|
2277
|
-
return "nw";
|
|
2278
|
-
}
|
|
2279
2272
|
function resolveSheetDirection(facing) {
|
|
2280
2273
|
switch (facing) {
|
|
2281
2274
|
case "se":
|
|
@@ -2296,22 +2289,8 @@ function frameRect(frame, row, columns, frameWidth, frameHeight) {
|
|
|
2296
2289
|
sh: frameHeight
|
|
2297
2290
|
};
|
|
2298
2291
|
}
|
|
2299
|
-
function getCurrentFrameFromDef(def, elapsed) {
|
|
2300
|
-
const frameDuration = 1e3 / def.frameRate;
|
|
2301
|
-
const totalDuration = def.frames * frameDuration;
|
|
2302
|
-
if (def.loop) {
|
|
2303
|
-
const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
|
|
2304
|
-
return { frame: frame2, finished: false };
|
|
2305
|
-
}
|
|
2306
|
-
if (elapsed >= totalDuration) {
|
|
2307
|
-
return { frame: def.frames - 1, finished: true };
|
|
2308
|
-
}
|
|
2309
|
-
const frame = Math.floor(elapsed / frameDuration);
|
|
2310
|
-
return { frame, finished: false };
|
|
2311
|
-
}
|
|
2312
2292
|
|
|
2313
2293
|
// components/game/molecules/useUnitSpriteAtlas.ts
|
|
2314
|
-
var WALK_HOLD_MS = 600;
|
|
2315
2294
|
function unitAtlasUrl(unit) {
|
|
2316
2295
|
if (unit.spriteSheet) return unit.spriteSheet;
|
|
2317
2296
|
const sprite = unit.sprite;
|
|
@@ -2333,9 +2312,6 @@ function useUnitSpriteAtlas(units) {
|
|
|
2333
2312
|
const loadingRef = useRef(/* @__PURE__ */ new Set());
|
|
2334
2313
|
const [pendingCount, setPendingCount] = useState(0);
|
|
2335
2314
|
const [, forceTick] = useState(0);
|
|
2336
|
-
const animStatesRef = useRef(/* @__PURE__ */ new Map());
|
|
2337
|
-
const lastTickRef = useRef(0);
|
|
2338
|
-
const rafRef = useRef(0);
|
|
2339
2315
|
const atlasUrls = useMemo(() => {
|
|
2340
2316
|
const set = /* @__PURE__ */ new Set();
|
|
2341
2317
|
for (const unit of units) {
|
|
@@ -2381,54 +2357,6 @@ function useUnitSpriteAtlas(units) {
|
|
|
2381
2357
|
}
|
|
2382
2358
|
return [...urls];
|
|
2383
2359
|
}, [units, pendingCount]);
|
|
2384
|
-
useEffect(() => {
|
|
2385
|
-
const hasAtlasUnits = units.some((u) => unitAtlasUrl(u) !== null);
|
|
2386
|
-
if (!hasAtlasUnits) return;
|
|
2387
|
-
let running = true;
|
|
2388
|
-
const tick = (ts) => {
|
|
2389
|
-
if (!running) return;
|
|
2390
|
-
const last = lastTickRef.current || ts;
|
|
2391
|
-
const delta = ts - last;
|
|
2392
|
-
lastTickRef.current = ts;
|
|
2393
|
-
const states = animStatesRef.current;
|
|
2394
|
-
const currentIds = /* @__PURE__ */ new Set();
|
|
2395
|
-
for (const unit of units) {
|
|
2396
|
-
if (unitAtlasUrl(unit) === null) continue;
|
|
2397
|
-
currentIds.add(unit.id);
|
|
2398
|
-
let state = states.get(unit.id);
|
|
2399
|
-
if (!state) {
|
|
2400
|
-
state = { animation: "idle", direction: "se", elapsed: 0, walkHold: 0, prev: null };
|
|
2401
|
-
states.set(unit.id, state);
|
|
2402
|
-
}
|
|
2403
|
-
const posX = unit.position?.x ?? unit.x ?? 0;
|
|
2404
|
-
const posY = unit.position?.y ?? unit.y ?? 0;
|
|
2405
|
-
if (state.prev) {
|
|
2406
|
-
const dx = posX - state.prev.x;
|
|
2407
|
-
const dy = posY - state.prev.y;
|
|
2408
|
-
if (dx !== 0 || dy !== 0) {
|
|
2409
|
-
state.animation = "walk";
|
|
2410
|
-
state.direction = inferDirection(dx, dy);
|
|
2411
|
-
state.walkHold = WALK_HOLD_MS;
|
|
2412
|
-
} else if (state.animation === "walk") {
|
|
2413
|
-
state.walkHold -= delta;
|
|
2414
|
-
if (state.walkHold <= 0) state.animation = "idle";
|
|
2415
|
-
}
|
|
2416
|
-
}
|
|
2417
|
-
state.prev = { x: posX, y: posY };
|
|
2418
|
-
state.elapsed += delta;
|
|
2419
|
-
}
|
|
2420
|
-
for (const id of states.keys()) {
|
|
2421
|
-
if (!currentIds.has(id)) states.delete(id);
|
|
2422
|
-
}
|
|
2423
|
-
rafRef.current = requestAnimationFrame(tick);
|
|
2424
|
-
};
|
|
2425
|
-
rafRef.current = requestAnimationFrame(tick);
|
|
2426
|
-
return () => {
|
|
2427
|
-
running = false;
|
|
2428
|
-
cancelAnimationFrame(rafRef.current);
|
|
2429
|
-
lastTickRef.current = 0;
|
|
2430
|
-
};
|
|
2431
|
-
}, [units]);
|
|
2432
2360
|
const resolveUnitFrame = useCallback((unitId) => {
|
|
2433
2361
|
const unit = units.find((u) => u.id === unitId);
|
|
2434
2362
|
if (!unit) return null;
|
|
@@ -2436,18 +2364,14 @@ function useUnitSpriteAtlas(units) {
|
|
|
2436
2364
|
if (!atlasUrl) return null;
|
|
2437
2365
|
const atlas = atlasCacheRef.current.get(atlasUrl);
|
|
2438
2366
|
if (!atlas) return null;
|
|
2439
|
-
const
|
|
2440
|
-
const
|
|
2441
|
-
const direction = state?.direction ?? "se";
|
|
2442
|
-
const elapsed = state?.elapsed ?? 0;
|
|
2367
|
+
const animation = unit.animation ?? "idle";
|
|
2368
|
+
const frame = unit.frame ?? 0;
|
|
2443
2369
|
const def = atlas.animations[animation] ?? atlas.animations.idle;
|
|
2444
2370
|
if (!def) return null;
|
|
2445
|
-
const { sheetDir, flipX } = resolveSheetDirection(
|
|
2371
|
+
const { sheetDir, flipX } = resolveSheetDirection("se");
|
|
2446
2372
|
const rel = atlas.sheets[sheetDir] ?? atlas.sheets.se ?? atlas.sheets.sw;
|
|
2447
2373
|
if (!rel) return null;
|
|
2448
2374
|
const sheetUrl = resolveSheetUrl(atlasUrl, rel);
|
|
2449
|
-
const isIdle = animation === "idle";
|
|
2450
|
-
const frame = isIdle ? 0 : getCurrentFrameFromDef(def, elapsed).frame;
|
|
2451
2375
|
const rect = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
|
|
2452
2376
|
return {
|
|
2453
2377
|
sheetUrl,
|
|
@@ -2456,7 +2380,7 @@ function useUnitSpriteAtlas(units) {
|
|
|
2456
2380
|
sw: rect.sw,
|
|
2457
2381
|
sh: rect.sh,
|
|
2458
2382
|
flipX,
|
|
2459
|
-
applyBreathing:
|
|
2383
|
+
applyBreathing: animation === "idle"
|
|
2460
2384
|
};
|
|
2461
2385
|
}, [units]);
|
|
2462
2386
|
return { sheetUrls, resolveUnitFrame, pendingCount };
|
|
@@ -2552,7 +2476,6 @@ var GameCanvas3D = forwardRef(
|
|
|
2552
2476
|
className,
|
|
2553
2477
|
isLoading: externalLoading,
|
|
2554
2478
|
error: externalError,
|
|
2555
|
-
entity,
|
|
2556
2479
|
preloadAssets = [],
|
|
2557
2480
|
tileClickEvent,
|
|
2558
2481
|
unitClickEvent,
|
|
@@ -23,8 +23,9 @@ export interface UseUnitSpriteAtlasResult {
|
|
|
23
23
|
pendingCount: number;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
* Load per-unit sprite atlases and
|
|
26
|
+
* Load per-unit sprite atlases and expose a pure frame resolver.
|
|
27
|
+
* Frame is computed from each unit's own `animation` + `frame` props — no internal clock.
|
|
27
28
|
*
|
|
28
|
-
* @param units - Units
|
|
29
|
+
* @param units - Units whose atlases should be loaded.
|
|
29
30
|
*/
|
|
30
31
|
export declare function useUnitSpriteAtlas(units: IsometricUnit[]): UseUnitSpriteAtlasResult;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AssetUrl } from '@almadar/core';
|
|
2
|
+
import type { AnimationName } from './spriteAnimation';
|
|
2
3
|
/**
|
|
3
4
|
* Isometric Game Types
|
|
4
5
|
*
|
|
@@ -7,6 +8,17 @@ import type { AssetUrl } from '@almadar/core';
|
|
|
7
8
|
*
|
|
8
9
|
* @packageDocumentation
|
|
9
10
|
*/
|
|
11
|
+
/** An active visual effect on the board. x,y are grid tile coords; ttl = remaining ticks. */
|
|
12
|
+
export interface ActiveEffect {
|
|
13
|
+
/** Unique key — matched against assetManifest.effects for sprite lookup */
|
|
14
|
+
key: string;
|
|
15
|
+
/** Grid x coordinate */
|
|
16
|
+
x: number;
|
|
17
|
+
/** Grid y coordinate */
|
|
18
|
+
y: number;
|
|
19
|
+
/** Remaining ticks — used as a fade/scale hint (alpha = min(1, ttl/4)) */
|
|
20
|
+
ttl: number;
|
|
21
|
+
}
|
|
10
22
|
/** A single isometric tile on the grid */
|
|
11
23
|
export interface IsometricTile {
|
|
12
24
|
/** Optional unique identifier (required for 3D rendering) */
|
|
@@ -85,6 +97,10 @@ export type IsometricUnit = {
|
|
|
85
97
|
};
|
|
86
98
|
/** Elevation offset for 3D rendering */
|
|
87
99
|
elevation?: number;
|
|
100
|
+
/** Animation name — driven by the LOLO state machine; renderer defaults to 'idle' */
|
|
101
|
+
animation?: AnimationName;
|
|
102
|
+
/** Frame index within the animation — driven by the LOLO state machine; renderer defaults to 0 */
|
|
103
|
+
frame?: number;
|
|
88
104
|
};
|
|
89
105
|
/** A map feature (resource, building, portal, etc.) */
|
|
90
106
|
export type IsometricFeature = {
|