@almadar/ui 2.9.0 → 2.9.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-A5J5CNCU.js → chunk-6D5QMEUS.js} +1 -1
- package/dist/{chunk-UVMBFEMO.js → chunk-7AKZ7SRV.js} +2 -2
- package/dist/{chunk-NES4SBB7.js → chunk-P6NZVIE5.js} +1355 -103
- package/dist/components/index.js +39 -1279
- package/dist/lib/index.js +1 -1
- package/dist/providers/index.js +3 -3
- package/dist/runtime/index.js +3 -3
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useTheme, useUISlots } from './chunk-DKQN5FVU.js';
|
|
2
2
|
import { useTranslate, useInfiniteScroll, useQuerySingleton, useLongPress, useSwipeGesture, useDragReorder, usePullToRefresh } from './chunk-WGJIL4YR.js';
|
|
3
3
|
import { useEventBus } from './chunk-YXZM3WCF.js';
|
|
4
|
-
import { cn, debugGroup, debug, debugGroupEnd, updateAssetStatus, bindCanvasCapture, getNestedValue, isDebugEnabled } from './chunk-
|
|
4
|
+
import { cn, debugGroup, debug, debugGroupEnd, updateAssetStatus, bindCanvasCapture, getNestedValue, isDebugEnabled } from './chunk-6D5QMEUS.js';
|
|
5
5
|
import { isPortalSlot } from './chunk-K2D5D3WK.js';
|
|
6
6
|
import { __publicField } from './chunk-PKBMQBKP.js';
|
|
7
7
|
import * as LucideIcons from 'lucide-react';
|
|
@@ -20,6 +20,7 @@ import dark from 'react-syntax-highlighter/dist/esm/styles/prism/vsc-dark-plus';
|
|
|
20
20
|
import { MapContainer, TileLayer, Marker, Popup, useMap } from 'react-leaflet';
|
|
21
21
|
import L from 'leaflet';
|
|
22
22
|
import 'leaflet/dist/leaflet.css';
|
|
23
|
+
import { getComponentForPattern as getComponentForPattern$1 } from '@almadar/patterns';
|
|
23
24
|
|
|
24
25
|
var iconAliases = {
|
|
25
26
|
"close": LucideIcons.X,
|
|
@@ -2638,30 +2639,30 @@ var ConfettiEffect = ({
|
|
|
2638
2639
|
),
|
|
2639
2640
|
"aria-hidden": "true",
|
|
2640
2641
|
children: [
|
|
2641
|
-
particles.map((
|
|
2642
|
-
const rad =
|
|
2643
|
-
const tx = Math.cos(rad) *
|
|
2644
|
-
const ty = Math.sin(rad) *
|
|
2642
|
+
particles.map((p2) => {
|
|
2643
|
+
const rad = p2.angle * Math.PI / 180;
|
|
2644
|
+
const tx = Math.cos(rad) * p2.distance;
|
|
2645
|
+
const ty = Math.sin(rad) * p2.distance - 20;
|
|
2645
2646
|
return /* @__PURE__ */ jsx(
|
|
2646
2647
|
Box,
|
|
2647
2648
|
{
|
|
2648
2649
|
className: "absolute rounded-sm",
|
|
2649
2650
|
style: {
|
|
2650
|
-
left: `${
|
|
2651
|
+
left: `${p2.left}%`,
|
|
2651
2652
|
top: "50%",
|
|
2652
|
-
width:
|
|
2653
|
-
height:
|
|
2654
|
-
backgroundColor:
|
|
2655
|
-
animation: `confetti-burst ${duration -
|
|
2653
|
+
width: p2.size,
|
|
2654
|
+
height: p2.size,
|
|
2655
|
+
backgroundColor: p2.color,
|
|
2656
|
+
animation: `confetti-burst ${duration - p2.delay}ms ease-out ${p2.delay}ms forwards`,
|
|
2656
2657
|
opacity: 0,
|
|
2657
2658
|
// Use CSS custom properties for the animation endpoint
|
|
2658
2659
|
// @ts-expect-error -- CSS custom properties are not typed in CSSProperties
|
|
2659
2660
|
"--confetti-tx": `${tx}px`,
|
|
2660
2661
|
"--confetti-ty": `${ty}px`,
|
|
2661
|
-
"--confetti-rotate": `${
|
|
2662
|
+
"--confetti-rotate": `${p2.rotation}deg`
|
|
2662
2663
|
}
|
|
2663
2664
|
},
|
|
2664
|
-
|
|
2665
|
+
p2.id
|
|
2665
2666
|
);
|
|
2666
2667
|
}),
|
|
2667
2668
|
/* @__PURE__ */ jsx("style", { children: `
|
|
@@ -7013,7 +7014,7 @@ var WizardProgress = ({
|
|
|
7013
7014
|
)
|
|
7014
7015
|
}
|
|
7015
7016
|
)
|
|
7016
|
-
] }, step.id);
|
|
7017
|
+
] }, step.id || `step-${index}`);
|
|
7017
7018
|
}) })
|
|
7018
7019
|
}
|
|
7019
7020
|
);
|
|
@@ -8084,11 +8085,13 @@ var LineChart = ({
|
|
|
8084
8085
|
className
|
|
8085
8086
|
}) => {
|
|
8086
8087
|
const gradientId = useId();
|
|
8088
|
+
const safeData = data ?? [];
|
|
8087
8089
|
const sortedData = useMemo(() => {
|
|
8088
|
-
return [
|
|
8090
|
+
if (safeData.length === 0) return [];
|
|
8091
|
+
return [...safeData].sort(
|
|
8089
8092
|
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
|
|
8090
8093
|
);
|
|
8091
|
-
}, [
|
|
8094
|
+
}, [safeData]);
|
|
8092
8095
|
const points = useMemo(() => {
|
|
8093
8096
|
if (sortedData.length === 0) return [];
|
|
8094
8097
|
const values = sortedData.map((d) => d.value);
|
|
@@ -8107,7 +8110,7 @@ var LineChart = ({
|
|
|
8107
8110
|
}, [sortedData, width, height]);
|
|
8108
8111
|
const linePath = useMemo(() => {
|
|
8109
8112
|
if (points.length === 0) return "";
|
|
8110
|
-
return points.map((
|
|
8113
|
+
return points.map((p2, i) => `${i === 0 ? "M" : "L"} ${p2.x} ${p2.y}`).join(" ");
|
|
8111
8114
|
}, [points]);
|
|
8112
8115
|
const areaPath = useMemo(() => {
|
|
8113
8116
|
if (points.length === 0 || !showArea) return "";
|
|
@@ -8116,7 +8119,7 @@ var LineChart = ({
|
|
|
8116
8119
|
const last = points[points.length - 1];
|
|
8117
8120
|
return `${linePath} L ${last.x} ${bottom} L ${first.x} ${bottom} Z`;
|
|
8118
8121
|
}, [linePath, points, height, showArea]);
|
|
8119
|
-
if (
|
|
8122
|
+
if (safeData.length === 0) {
|
|
8120
8123
|
return /* @__PURE__ */ jsx(Box, { className: cn("flex items-center justify-center text-[var(--color-muted-foreground)]", className), style: { width, height }, children: "No data" });
|
|
8121
8124
|
}
|
|
8122
8125
|
return /* @__PURE__ */ jsx(Box, { className: cn(className), children: /* @__PURE__ */ jsxs(
|
|
@@ -10308,7 +10311,7 @@ function IsometricCanvas({
|
|
|
10308
10311
|
resolveUnitFrame,
|
|
10309
10312
|
effectSpriteUrls = [],
|
|
10310
10313
|
onDrawEffects,
|
|
10311
|
-
hasActiveEffects = false,
|
|
10314
|
+
hasActiveEffects: hasActiveEffects2 = false,
|
|
10312
10315
|
// Tuning
|
|
10313
10316
|
diamondTopY: diamondTopYProp,
|
|
10314
10317
|
// Remote asset loading
|
|
@@ -10365,10 +10368,10 @@ function IsometricCanvas({
|
|
|
10365
10368
|
return (gridHeight - 1) * (scaledTileWidth / 2);
|
|
10366
10369
|
}, [gridHeight, scaledTileWidth]);
|
|
10367
10370
|
const validMoveSet = useMemo(() => {
|
|
10368
|
-
return new Set(validMoves.map((
|
|
10371
|
+
return new Set(validMoves.map((p2) => `${p2.x},${p2.y}`));
|
|
10369
10372
|
}, [validMoves]);
|
|
10370
10373
|
const attackTargetSet = useMemo(() => {
|
|
10371
|
-
return new Set(attackTargets.map((
|
|
10374
|
+
return new Set(attackTargets.map((p2) => `${p2.x},${p2.y}`));
|
|
10372
10375
|
}, [attackTargets]);
|
|
10373
10376
|
const resolveManifestUrl = useCallback((relativePath) => {
|
|
10374
10377
|
if (!relativePath) return void 0;
|
|
@@ -10461,10 +10464,10 @@ function IsometricCanvas({
|
|
|
10461
10464
|
miniCanvas.height = mH;
|
|
10462
10465
|
mCtx.clearRect(0, 0, mW, mH);
|
|
10463
10466
|
const allScreenPos = sortedTiles.map((t2) => isoToScreen(t2.x, t2.y, scale, baseOffsetX));
|
|
10464
|
-
const minX = Math.min(...allScreenPos.map((
|
|
10465
|
-
const maxX = Math.max(...allScreenPos.map((
|
|
10466
|
-
const minY = Math.min(...allScreenPos.map((
|
|
10467
|
-
const maxY = Math.max(...allScreenPos.map((
|
|
10467
|
+
const minX = Math.min(...allScreenPos.map((p2) => p2.x));
|
|
10468
|
+
const maxX = Math.max(...allScreenPos.map((p2) => p2.x + scaledTileWidth));
|
|
10469
|
+
const minY = Math.min(...allScreenPos.map((p2) => p2.y));
|
|
10470
|
+
const maxY = Math.max(...allScreenPos.map((p2) => p2.y + scaledTileHeight));
|
|
10468
10471
|
const worldW = maxX - minX;
|
|
10469
10472
|
const worldH = maxY - minY;
|
|
10470
10473
|
const scaleM = Math.min(mW / worldW, mH / worldH) * 0.9;
|
|
@@ -10846,7 +10849,7 @@ function IsometricCanvas({
|
|
|
10846
10849
|
};
|
|
10847
10850
|
}, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
|
|
10848
10851
|
useEffect(() => {
|
|
10849
|
-
const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null ||
|
|
10852
|
+
const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2;
|
|
10850
10853
|
draw(animTimeRef.current);
|
|
10851
10854
|
if (!hasAnimations) return;
|
|
10852
10855
|
let running = true;
|
|
@@ -10862,7 +10865,7 @@ function IsometricCanvas({
|
|
|
10862
10865
|
running = false;
|
|
10863
10866
|
cancelAnimationFrame(rafIdRef.current);
|
|
10864
10867
|
};
|
|
10865
|
-
}, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId,
|
|
10868
|
+
}, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId, hasActiveEffects2, lerpToTarget, targetCameraRef]);
|
|
10866
10869
|
const handleMouseMoveWithCamera = useCallback((e) => {
|
|
10867
10870
|
if (enableCamera) {
|
|
10868
10871
|
const wasPanning = handleMouseMove(e, () => draw(animTimeRef.current));
|
|
@@ -12855,19 +12858,20 @@ var Meter = ({
|
|
|
12855
12858
|
},
|
|
12856
12859
|
[eventBus, value]
|
|
12857
12860
|
);
|
|
12861
|
+
const safeVal = value ?? 0;
|
|
12858
12862
|
const percentage = useMemo(() => {
|
|
12859
12863
|
const range = max - min;
|
|
12860
12864
|
if (range <= 0) return 0;
|
|
12861
|
-
return Math.min(Math.max((
|
|
12862
|
-
}, [
|
|
12865
|
+
return Math.min(Math.max((safeVal - min) / range * 100, 0), 100);
|
|
12866
|
+
}, [safeVal, min, max]);
|
|
12863
12867
|
const activeColor = useMemo(
|
|
12864
|
-
() => getColorForValue(
|
|
12865
|
-
[
|
|
12868
|
+
() => getColorForValue(safeVal, max, thresholds),
|
|
12869
|
+
[safeVal, max, thresholds]
|
|
12866
12870
|
);
|
|
12867
12871
|
const displayValue = useMemo(() => {
|
|
12868
|
-
const formatted = Number.isInteger(
|
|
12872
|
+
const formatted = Number.isInteger(safeVal) ? safeVal : safeVal.toFixed(1);
|
|
12869
12873
|
return unit ? `${formatted}${unit}` : `${formatted}`;
|
|
12870
|
-
}, [
|
|
12874
|
+
}, [safeVal, unit]);
|
|
12871
12875
|
if (isLoading) {
|
|
12872
12876
|
return /* @__PURE__ */ jsx(LoadingState, { message: "Loading meter...", className });
|
|
12873
12877
|
}
|
|
@@ -15592,6 +15596,1246 @@ function MasterDetail({
|
|
|
15592
15596
|
);
|
|
15593
15597
|
}
|
|
15594
15598
|
MasterDetail.displayName = "MasterDetail";
|
|
15599
|
+
|
|
15600
|
+
// components/organisms/game/types/effects.ts
|
|
15601
|
+
var EMPTY_EFFECT_STATE = {
|
|
15602
|
+
particles: [],
|
|
15603
|
+
sequences: [],
|
|
15604
|
+
overlays: []
|
|
15605
|
+
};
|
|
15606
|
+
|
|
15607
|
+
// components/organisms/game/utils/canvasEffects.ts
|
|
15608
|
+
var _offscreen = null;
|
|
15609
|
+
var _offCtx = null;
|
|
15610
|
+
function getOffscreenCtx(w, h) {
|
|
15611
|
+
if (!_offscreen) {
|
|
15612
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
15613
|
+
_offscreen = new OffscreenCanvas(w, h);
|
|
15614
|
+
} else {
|
|
15615
|
+
_offscreen = document.createElement("canvas");
|
|
15616
|
+
}
|
|
15617
|
+
}
|
|
15618
|
+
if (_offscreen.width < w) _offscreen.width = w;
|
|
15619
|
+
if (_offscreen.height < h) _offscreen.height = h;
|
|
15620
|
+
if (!_offCtx) {
|
|
15621
|
+
_offCtx = _offscreen.getContext("2d");
|
|
15622
|
+
}
|
|
15623
|
+
return _offCtx;
|
|
15624
|
+
}
|
|
15625
|
+
function drawTintedImage(ctx, img, x, y, w, h, tint, alpha, blendMode = "source-over") {
|
|
15626
|
+
if (w <= 0 || h <= 0) return;
|
|
15627
|
+
const oc = getOffscreenCtx(w, h);
|
|
15628
|
+
oc.clearRect(0, 0, w, h);
|
|
15629
|
+
oc.globalCompositeOperation = "source-over";
|
|
15630
|
+
oc.drawImage(img, 0, 0, w, h);
|
|
15631
|
+
oc.globalCompositeOperation = "source-atop";
|
|
15632
|
+
oc.fillStyle = `rgb(${tint.r}, ${tint.g}, ${tint.b})`;
|
|
15633
|
+
oc.fillRect(0, 0, w, h);
|
|
15634
|
+
const prevAlpha = ctx.globalAlpha;
|
|
15635
|
+
const prevBlend = ctx.globalCompositeOperation;
|
|
15636
|
+
ctx.globalAlpha = alpha;
|
|
15637
|
+
ctx.globalCompositeOperation = blendMode;
|
|
15638
|
+
ctx.drawImage(_offscreen, 0, 0, w, h, x, y, w, h);
|
|
15639
|
+
ctx.globalAlpha = prevAlpha;
|
|
15640
|
+
ctx.globalCompositeOperation = prevBlend;
|
|
15641
|
+
}
|
|
15642
|
+
function randRange(min, max) {
|
|
15643
|
+
return min + Math.random() * (max - min);
|
|
15644
|
+
}
|
|
15645
|
+
function spawnParticles(config, animTime) {
|
|
15646
|
+
const particles = [];
|
|
15647
|
+
for (let i = 0; i < config.count; i++) {
|
|
15648
|
+
const angle = randRange(config.angleMin, config.angleMax);
|
|
15649
|
+
const speed = randRange(config.velocityMin, config.velocityMax);
|
|
15650
|
+
const spriteUrl = config.spriteUrls[Math.floor(Math.random() * config.spriteUrls.length)];
|
|
15651
|
+
particles.push({
|
|
15652
|
+
spriteUrl,
|
|
15653
|
+
x: config.originX + randRange(-config.spread, config.spread),
|
|
15654
|
+
y: config.originY + randRange(-config.spread, config.spread),
|
|
15655
|
+
vx: Math.cos(angle) * speed,
|
|
15656
|
+
vy: Math.sin(angle) * speed,
|
|
15657
|
+
gravity: config.gravity,
|
|
15658
|
+
rotation: Math.random() * Math.PI * 2,
|
|
15659
|
+
rotationSpeed: randRange(config.rotationSpeedMin ?? -2, config.rotationSpeedMax ?? 2),
|
|
15660
|
+
scale: randRange(config.scaleMin, config.scaleMax),
|
|
15661
|
+
scaleSpeed: config.scaleSpeed ?? 0,
|
|
15662
|
+
alpha: config.alpha ?? 1,
|
|
15663
|
+
fadeRate: config.fadeRate ?? -1.5,
|
|
15664
|
+
tint: { ...config.tint },
|
|
15665
|
+
blendMode: config.blendMode ?? "source-over",
|
|
15666
|
+
spawnTime: animTime,
|
|
15667
|
+
lifetime: randRange(config.lifetimeMin, config.lifetimeMax)
|
|
15668
|
+
});
|
|
15669
|
+
}
|
|
15670
|
+
return particles;
|
|
15671
|
+
}
|
|
15672
|
+
function spawnSequence(config, animTime) {
|
|
15673
|
+
return {
|
|
15674
|
+
frameUrls: config.frameUrls,
|
|
15675
|
+
x: config.originX,
|
|
15676
|
+
y: config.originY,
|
|
15677
|
+
frameDuration: config.frameDuration,
|
|
15678
|
+
startTime: animTime,
|
|
15679
|
+
loop: config.loop ?? false,
|
|
15680
|
+
scale: config.scale ?? 1,
|
|
15681
|
+
tint: config.tint ?? null,
|
|
15682
|
+
alpha: config.alpha ?? 1,
|
|
15683
|
+
blendMode: config.blendMode ?? "source-over"
|
|
15684
|
+
};
|
|
15685
|
+
}
|
|
15686
|
+
function spawnOverlay(config, animTime) {
|
|
15687
|
+
return {
|
|
15688
|
+
spriteUrl: config.spriteUrl,
|
|
15689
|
+
x: config.originX,
|
|
15690
|
+
y: config.originY,
|
|
15691
|
+
alpha: config.alpha ?? 0.8,
|
|
15692
|
+
fadeRate: config.fadeRate ?? -0.5,
|
|
15693
|
+
pulseAmplitude: config.pulseAmplitude ?? 0,
|
|
15694
|
+
pulseFrequency: config.pulseFrequency ?? 2,
|
|
15695
|
+
scale: config.scale ?? 1,
|
|
15696
|
+
blendMode: config.blendMode ?? "source-over",
|
|
15697
|
+
spawnTime: animTime,
|
|
15698
|
+
lifetime: config.lifetime ?? 2e3
|
|
15699
|
+
};
|
|
15700
|
+
}
|
|
15701
|
+
function updateEffectState(state, animTime, deltaMs) {
|
|
15702
|
+
const dt = deltaMs / 1e3;
|
|
15703
|
+
const particles = state.particles.map((p2) => ({
|
|
15704
|
+
...p2,
|
|
15705
|
+
x: p2.x + p2.vx * dt,
|
|
15706
|
+
y: p2.y + p2.vy * dt,
|
|
15707
|
+
vy: p2.vy + p2.gravity * dt,
|
|
15708
|
+
rotation: p2.rotation + p2.rotationSpeed * dt,
|
|
15709
|
+
scale: Math.max(0, p2.scale + p2.scaleSpeed * dt),
|
|
15710
|
+
alpha: Math.max(0, p2.alpha + p2.fadeRate * dt)
|
|
15711
|
+
})).filter((p2) => p2.alpha > 0.01 && animTime - p2.spawnTime < p2.lifetime);
|
|
15712
|
+
const sequences = state.sequences.filter((s) => {
|
|
15713
|
+
const elapsed = animTime - s.startTime;
|
|
15714
|
+
const totalDuration = s.frameUrls.length * s.frameDuration;
|
|
15715
|
+
return s.loop || elapsed < totalDuration;
|
|
15716
|
+
});
|
|
15717
|
+
const overlays = state.overlays.map((o) => ({
|
|
15718
|
+
...o,
|
|
15719
|
+
alpha: Math.max(0, o.alpha + o.fadeRate * dt)
|
|
15720
|
+
})).filter((o) => o.alpha > 0.01 && animTime - o.spawnTime < o.lifetime);
|
|
15721
|
+
return { particles, sequences, overlays };
|
|
15722
|
+
}
|
|
15723
|
+
function drawEffectState(ctx, state, animTime, getImage) {
|
|
15724
|
+
for (const o of state.overlays) {
|
|
15725
|
+
const img = getImage(o.spriteUrl);
|
|
15726
|
+
if (!img) continue;
|
|
15727
|
+
let alpha = o.alpha;
|
|
15728
|
+
if (o.pulseAmplitude > 0) {
|
|
15729
|
+
const elapsed = (animTime - o.spawnTime) / 1e3;
|
|
15730
|
+
alpha += Math.sin(elapsed * o.pulseFrequency * Math.PI * 2) * o.pulseAmplitude;
|
|
15731
|
+
alpha = Math.max(0, Math.min(1, alpha));
|
|
15732
|
+
}
|
|
15733
|
+
const w = img.naturalWidth * o.scale;
|
|
15734
|
+
const h = img.naturalHeight * o.scale;
|
|
15735
|
+
const prevAlpha = ctx.globalAlpha;
|
|
15736
|
+
const prevBlend = ctx.globalCompositeOperation;
|
|
15737
|
+
ctx.globalAlpha = alpha;
|
|
15738
|
+
ctx.globalCompositeOperation = o.blendMode;
|
|
15739
|
+
ctx.drawImage(img, o.x - w / 2, o.y - h / 2, w, h);
|
|
15740
|
+
ctx.globalAlpha = prevAlpha;
|
|
15741
|
+
ctx.globalCompositeOperation = prevBlend;
|
|
15742
|
+
}
|
|
15743
|
+
for (const s of state.sequences) {
|
|
15744
|
+
const elapsed = animTime - s.startTime;
|
|
15745
|
+
let frameIndex = Math.floor(elapsed / s.frameDuration);
|
|
15746
|
+
if (s.loop) {
|
|
15747
|
+
frameIndex = frameIndex % s.frameUrls.length;
|
|
15748
|
+
} else if (frameIndex >= s.frameUrls.length) {
|
|
15749
|
+
continue;
|
|
15750
|
+
}
|
|
15751
|
+
const img = getImage(s.frameUrls[frameIndex]);
|
|
15752
|
+
if (!img) continue;
|
|
15753
|
+
const w = img.naturalWidth * s.scale;
|
|
15754
|
+
const h = img.naturalHeight * s.scale;
|
|
15755
|
+
if (s.tint) {
|
|
15756
|
+
drawTintedImage(ctx, img, s.x - w / 2, s.y - h / 2, w, h, s.tint, s.alpha, s.blendMode);
|
|
15757
|
+
} else {
|
|
15758
|
+
const prevAlpha = ctx.globalAlpha;
|
|
15759
|
+
const prevBlend = ctx.globalCompositeOperation;
|
|
15760
|
+
ctx.globalAlpha = s.alpha;
|
|
15761
|
+
ctx.globalCompositeOperation = s.blendMode;
|
|
15762
|
+
ctx.drawImage(img, s.x - w / 2, s.y - h / 2, w, h);
|
|
15763
|
+
ctx.globalAlpha = prevAlpha;
|
|
15764
|
+
ctx.globalCompositeOperation = prevBlend;
|
|
15765
|
+
}
|
|
15766
|
+
}
|
|
15767
|
+
for (const p2 of state.particles) {
|
|
15768
|
+
const img = getImage(p2.spriteUrl);
|
|
15769
|
+
if (!img) continue;
|
|
15770
|
+
const w = img.naturalWidth * p2.scale;
|
|
15771
|
+
const h = img.naturalHeight * p2.scale;
|
|
15772
|
+
ctx.save();
|
|
15773
|
+
ctx.translate(p2.x, p2.y);
|
|
15774
|
+
ctx.rotate(p2.rotation);
|
|
15775
|
+
drawTintedImage(ctx, img, -w / 2, -h / 2, w, h, p2.tint, p2.alpha, p2.blendMode);
|
|
15776
|
+
ctx.restore();
|
|
15777
|
+
}
|
|
15778
|
+
}
|
|
15779
|
+
function hasActiveEffects(state) {
|
|
15780
|
+
return state.particles.length > 0 || state.sequences.length > 0 || state.overlays.length > 0;
|
|
15781
|
+
}
|
|
15782
|
+
function getAllEffectSpriteUrls(manifest) {
|
|
15783
|
+
const urls = [];
|
|
15784
|
+
const base = manifest.baseUrl;
|
|
15785
|
+
if (manifest.particles) {
|
|
15786
|
+
for (const value of Object.values(manifest.particles)) {
|
|
15787
|
+
if (Array.isArray(value)) {
|
|
15788
|
+
value.forEach((v) => urls.push(`${base}/${v}`));
|
|
15789
|
+
} else if (typeof value === "string") {
|
|
15790
|
+
urls.push(`${base}/${value}`);
|
|
15791
|
+
}
|
|
15792
|
+
}
|
|
15793
|
+
}
|
|
15794
|
+
if (manifest.animations) {
|
|
15795
|
+
for (const frames of Object.values(manifest.animations)) {
|
|
15796
|
+
if (Array.isArray(frames)) {
|
|
15797
|
+
frames.forEach((f) => urls.push(`${base}/${f}`));
|
|
15798
|
+
}
|
|
15799
|
+
}
|
|
15800
|
+
}
|
|
15801
|
+
return urls;
|
|
15802
|
+
}
|
|
15803
|
+
|
|
15804
|
+
// components/organisms/game/utils/combatPresets.ts
|
|
15805
|
+
var PI = Math.PI;
|
|
15806
|
+
function p(manifest, key) {
|
|
15807
|
+
const particles = manifest.particles;
|
|
15808
|
+
if (!particles) return [];
|
|
15809
|
+
const val = particles[key];
|
|
15810
|
+
if (Array.isArray(val)) return val.map((v) => `${manifest.baseUrl}/${v}`);
|
|
15811
|
+
if (typeof val === "string") return [`${manifest.baseUrl}/${val}`];
|
|
15812
|
+
return [];
|
|
15813
|
+
}
|
|
15814
|
+
function anim(manifest, key) {
|
|
15815
|
+
const animations = manifest.animations;
|
|
15816
|
+
if (!animations) return [];
|
|
15817
|
+
const val = animations[key];
|
|
15818
|
+
if (Array.isArray(val)) return val.map((v) => `${manifest.baseUrl}/${v}`);
|
|
15819
|
+
return [];
|
|
15820
|
+
}
|
|
15821
|
+
function createCombatPresets(manifest) {
|
|
15822
|
+
return {
|
|
15823
|
+
// =====================================================================
|
|
15824
|
+
// MELEE — slash (red) + dirt + scratch + flash sequence
|
|
15825
|
+
// =====================================================================
|
|
15826
|
+
melee: (originX, originY) => {
|
|
15827
|
+
const particles = [
|
|
15828
|
+
{
|
|
15829
|
+
spriteUrls: p(manifest, "slash"),
|
|
15830
|
+
count: 6,
|
|
15831
|
+
originX,
|
|
15832
|
+
originY,
|
|
15833
|
+
spread: 8,
|
|
15834
|
+
velocityMin: 40,
|
|
15835
|
+
velocityMax: 120,
|
|
15836
|
+
angleMin: -PI * 0.8,
|
|
15837
|
+
angleMax: -PI * 0.2,
|
|
15838
|
+
gravity: 0,
|
|
15839
|
+
tint: { r: 255, g: 60, b: 40 },
|
|
15840
|
+
scaleMin: 0.3,
|
|
15841
|
+
scaleMax: 0.6,
|
|
15842
|
+
lifetimeMin: 300,
|
|
15843
|
+
lifetimeMax: 500,
|
|
15844
|
+
fadeRate: -2.5
|
|
15845
|
+
},
|
|
15846
|
+
{
|
|
15847
|
+
spriteUrls: p(manifest, "dirt"),
|
|
15848
|
+
count: 4,
|
|
15849
|
+
originX,
|
|
15850
|
+
originY: originY + 10,
|
|
15851
|
+
spread: 12,
|
|
15852
|
+
velocityMin: 20,
|
|
15853
|
+
velocityMax: 60,
|
|
15854
|
+
angleMin: -PI * 0.9,
|
|
15855
|
+
angleMax: -PI * 0.1,
|
|
15856
|
+
gravity: 120,
|
|
15857
|
+
tint: { r: 180, g: 140, b: 90 },
|
|
15858
|
+
scaleMin: 0.15,
|
|
15859
|
+
scaleMax: 0.3,
|
|
15860
|
+
lifetimeMin: 400,
|
|
15861
|
+
lifetimeMax: 700,
|
|
15862
|
+
fadeRate: -1.8
|
|
15863
|
+
},
|
|
15864
|
+
{
|
|
15865
|
+
spriteUrls: p(manifest, "scratch"),
|
|
15866
|
+
count: 2,
|
|
15867
|
+
originX,
|
|
15868
|
+
originY,
|
|
15869
|
+
spread: 5,
|
|
15870
|
+
velocityMin: 10,
|
|
15871
|
+
velocityMax: 30,
|
|
15872
|
+
angleMin: -PI * 0.7,
|
|
15873
|
+
angleMax: -PI * 0.3,
|
|
15874
|
+
gravity: 0,
|
|
15875
|
+
tint: { r: 255, g: 200, b: 150 },
|
|
15876
|
+
scaleMin: 0.25,
|
|
15877
|
+
scaleMax: 0.4,
|
|
15878
|
+
lifetimeMin: 200,
|
|
15879
|
+
lifetimeMax: 400,
|
|
15880
|
+
fadeRate: -3
|
|
15881
|
+
}
|
|
15882
|
+
];
|
|
15883
|
+
const sequences = [];
|
|
15884
|
+
const flashFrames = anim(manifest, "flash");
|
|
15885
|
+
if (flashFrames.length > 0) {
|
|
15886
|
+
sequences.push({
|
|
15887
|
+
frameUrls: flashFrames,
|
|
15888
|
+
originX,
|
|
15889
|
+
originY,
|
|
15890
|
+
frameDuration: 35,
|
|
15891
|
+
scale: 0.4
|
|
15892
|
+
});
|
|
15893
|
+
}
|
|
15894
|
+
return {
|
|
15895
|
+
particles: particles.filter((pc) => pc.spriteUrls.length > 0),
|
|
15896
|
+
sequences,
|
|
15897
|
+
overlays: [],
|
|
15898
|
+
screenShake: 4,
|
|
15899
|
+
screenFlash: null
|
|
15900
|
+
};
|
|
15901
|
+
},
|
|
15902
|
+
// =====================================================================
|
|
15903
|
+
// RANGED — muzzle + trace + smoke + explosion sequence
|
|
15904
|
+
// =====================================================================
|
|
15905
|
+
ranged: (originX, originY) => {
|
|
15906
|
+
const particles = [
|
|
15907
|
+
{
|
|
15908
|
+
spriteUrls: p(manifest, "muzzle"),
|
|
15909
|
+
count: 3,
|
|
15910
|
+
originX,
|
|
15911
|
+
originY,
|
|
15912
|
+
spread: 4,
|
|
15913
|
+
velocityMin: 60,
|
|
15914
|
+
velocityMax: 150,
|
|
15915
|
+
angleMin: -PI * 0.6,
|
|
15916
|
+
angleMax: -PI * 0.4,
|
|
15917
|
+
gravity: 0,
|
|
15918
|
+
tint: { r: 255, g: 220, b: 100 },
|
|
15919
|
+
scaleMin: 0.2,
|
|
15920
|
+
scaleMax: 0.4,
|
|
15921
|
+
lifetimeMin: 200,
|
|
15922
|
+
lifetimeMax: 400,
|
|
15923
|
+
fadeRate: -3
|
|
15924
|
+
},
|
|
15925
|
+
{
|
|
15926
|
+
spriteUrls: p(manifest, "trace"),
|
|
15927
|
+
count: 5,
|
|
15928
|
+
originX,
|
|
15929
|
+
originY,
|
|
15930
|
+
spread: 3,
|
|
15931
|
+
velocityMin: 100,
|
|
15932
|
+
velocityMax: 200,
|
|
15933
|
+
angleMin: -PI * 0.55,
|
|
15934
|
+
angleMax: -PI * 0.45,
|
|
15935
|
+
gravity: 0,
|
|
15936
|
+
tint: { r: 255, g: 200, b: 80 },
|
|
15937
|
+
scaleMin: 0.15,
|
|
15938
|
+
scaleMax: 0.3,
|
|
15939
|
+
lifetimeMin: 150,
|
|
15940
|
+
lifetimeMax: 300,
|
|
15941
|
+
fadeRate: -4
|
|
15942
|
+
},
|
|
15943
|
+
{
|
|
15944
|
+
spriteUrls: p(manifest, "smoke").slice(0, 3),
|
|
15945
|
+
count: 3,
|
|
15946
|
+
originX,
|
|
15947
|
+
originY: originY + 5,
|
|
15948
|
+
spread: 6,
|
|
15949
|
+
velocityMin: 10,
|
|
15950
|
+
velocityMax: 30,
|
|
15951
|
+
angleMin: -PI * 0.8,
|
|
15952
|
+
angleMax: -PI * 0.2,
|
|
15953
|
+
gravity: -20,
|
|
15954
|
+
tint: { r: 200, g: 200, b: 200 },
|
|
15955
|
+
scaleMin: 0.2,
|
|
15956
|
+
scaleMax: 0.35,
|
|
15957
|
+
lifetimeMin: 500,
|
|
15958
|
+
lifetimeMax: 800,
|
|
15959
|
+
fadeRate: -1.5
|
|
15960
|
+
}
|
|
15961
|
+
];
|
|
15962
|
+
const sequences = [];
|
|
15963
|
+
const explosionFrames = anim(manifest, "smokeExplosion");
|
|
15964
|
+
if (explosionFrames.length > 0) {
|
|
15965
|
+
sequences.push({
|
|
15966
|
+
frameUrls: explosionFrames,
|
|
15967
|
+
originX,
|
|
15968
|
+
originY,
|
|
15969
|
+
frameDuration: 50,
|
|
15970
|
+
scale: 0.35
|
|
15971
|
+
});
|
|
15972
|
+
}
|
|
15973
|
+
return {
|
|
15974
|
+
particles: particles.filter((pc) => pc.spriteUrls.length > 0),
|
|
15975
|
+
sequences,
|
|
15976
|
+
overlays: [],
|
|
15977
|
+
screenShake: 2,
|
|
15978
|
+
screenFlash: null
|
|
15979
|
+
};
|
|
15980
|
+
},
|
|
15981
|
+
// =====================================================================
|
|
15982
|
+
// MAGIC — twirl (purple) + spark (purple) + star
|
|
15983
|
+
// =====================================================================
|
|
15984
|
+
magic: (originX, originY) => {
|
|
15985
|
+
const particles = [
|
|
15986
|
+
{
|
|
15987
|
+
spriteUrls: p(manifest, "twirl"),
|
|
15988
|
+
count: 5,
|
|
15989
|
+
originX,
|
|
15990
|
+
originY,
|
|
15991
|
+
spread: 15,
|
|
15992
|
+
velocityMin: 20,
|
|
15993
|
+
velocityMax: 80,
|
|
15994
|
+
angleMin: 0,
|
|
15995
|
+
angleMax: PI * 2,
|
|
15996
|
+
gravity: -30,
|
|
15997
|
+
tint: { r: 180, g: 80, b: 255 },
|
|
15998
|
+
scaleMin: 0.2,
|
|
15999
|
+
scaleMax: 0.5,
|
|
16000
|
+
lifetimeMin: 500,
|
|
16001
|
+
lifetimeMax: 900,
|
|
16002
|
+
fadeRate: -1.2,
|
|
16003
|
+
blendMode: "lighter",
|
|
16004
|
+
rotationSpeedMin: -4,
|
|
16005
|
+
rotationSpeedMax: 4
|
|
16006
|
+
},
|
|
16007
|
+
{
|
|
16008
|
+
spriteUrls: p(manifest, "spark"),
|
|
16009
|
+
count: 8,
|
|
16010
|
+
originX,
|
|
16011
|
+
originY,
|
|
16012
|
+
spread: 20,
|
|
16013
|
+
velocityMin: 30,
|
|
16014
|
+
velocityMax: 100,
|
|
16015
|
+
angleMin: 0,
|
|
16016
|
+
angleMax: PI * 2,
|
|
16017
|
+
gravity: -15,
|
|
16018
|
+
tint: { r: 200, g: 120, b: 255 },
|
|
16019
|
+
scaleMin: 0.1,
|
|
16020
|
+
scaleMax: 0.25,
|
|
16021
|
+
lifetimeMin: 300,
|
|
16022
|
+
lifetimeMax: 600,
|
|
16023
|
+
fadeRate: -2,
|
|
16024
|
+
blendMode: "lighter"
|
|
16025
|
+
},
|
|
16026
|
+
{
|
|
16027
|
+
spriteUrls: p(manifest, "star"),
|
|
16028
|
+
count: 4,
|
|
16029
|
+
originX,
|
|
16030
|
+
originY,
|
|
16031
|
+
spread: 10,
|
|
16032
|
+
velocityMin: 15,
|
|
16033
|
+
velocityMax: 50,
|
|
16034
|
+
angleMin: -PI,
|
|
16035
|
+
angleMax: 0,
|
|
16036
|
+
gravity: -40,
|
|
16037
|
+
tint: { r: 220, g: 180, b: 255 },
|
|
16038
|
+
scaleMin: 0.15,
|
|
16039
|
+
scaleMax: 0.3,
|
|
16040
|
+
lifetimeMin: 600,
|
|
16041
|
+
lifetimeMax: 1e3,
|
|
16042
|
+
fadeRate: -1,
|
|
16043
|
+
blendMode: "lighter"
|
|
16044
|
+
}
|
|
16045
|
+
];
|
|
16046
|
+
const overlays = [];
|
|
16047
|
+
const circleUrls = p(manifest, "circle");
|
|
16048
|
+
if (circleUrls.length > 0) {
|
|
16049
|
+
overlays.push({
|
|
16050
|
+
spriteUrl: circleUrls[0],
|
|
16051
|
+
originX,
|
|
16052
|
+
originY,
|
|
16053
|
+
alpha: 0.5,
|
|
16054
|
+
fadeRate: -0.6,
|
|
16055
|
+
pulseAmplitude: 0.2,
|
|
16056
|
+
pulseFrequency: 3,
|
|
16057
|
+
scale: 0.5,
|
|
16058
|
+
blendMode: "lighter",
|
|
16059
|
+
lifetime: 1200
|
|
16060
|
+
});
|
|
16061
|
+
}
|
|
16062
|
+
return {
|
|
16063
|
+
particles: particles.filter((pc) => pc.spriteUrls.length > 0),
|
|
16064
|
+
sequences: [],
|
|
16065
|
+
overlays,
|
|
16066
|
+
screenShake: 0,
|
|
16067
|
+
screenFlash: null
|
|
16068
|
+
};
|
|
16069
|
+
},
|
|
16070
|
+
// =====================================================================
|
|
16071
|
+
// HEAL — circle (green) + star (green) + light (green, pulse)
|
|
16072
|
+
// =====================================================================
|
|
16073
|
+
heal: (originX, originY) => {
|
|
16074
|
+
const particles = [
|
|
16075
|
+
{
|
|
16076
|
+
spriteUrls: p(manifest, "circle"),
|
|
16077
|
+
count: 6,
|
|
16078
|
+
originX,
|
|
16079
|
+
originY,
|
|
16080
|
+
spread: 15,
|
|
16081
|
+
velocityMin: 10,
|
|
16082
|
+
velocityMax: 40,
|
|
16083
|
+
angleMin: -PI,
|
|
16084
|
+
angleMax: -PI * 0.3,
|
|
16085
|
+
gravity: -50,
|
|
16086
|
+
tint: { r: 80, g: 255, b: 120 },
|
|
16087
|
+
scaleMin: 0.15,
|
|
16088
|
+
scaleMax: 0.35,
|
|
16089
|
+
lifetimeMin: 600,
|
|
16090
|
+
lifetimeMax: 1e3,
|
|
16091
|
+
fadeRate: -0.8,
|
|
16092
|
+
blendMode: "lighter"
|
|
16093
|
+
},
|
|
16094
|
+
{
|
|
16095
|
+
spriteUrls: p(manifest, "star"),
|
|
16096
|
+
count: 5,
|
|
16097
|
+
originX,
|
|
16098
|
+
originY,
|
|
16099
|
+
spread: 12,
|
|
16100
|
+
velocityMin: 15,
|
|
16101
|
+
velocityMax: 50,
|
|
16102
|
+
angleMin: -PI * 0.9,
|
|
16103
|
+
angleMax: -PI * 0.1,
|
|
16104
|
+
gravity: -60,
|
|
16105
|
+
tint: { r: 100, g: 255, b: 140 },
|
|
16106
|
+
scaleMin: 0.1,
|
|
16107
|
+
scaleMax: 0.2,
|
|
16108
|
+
lifetimeMin: 500,
|
|
16109
|
+
lifetimeMax: 800,
|
|
16110
|
+
fadeRate: -1.2,
|
|
16111
|
+
blendMode: "lighter"
|
|
16112
|
+
}
|
|
16113
|
+
];
|
|
16114
|
+
const overlays = [];
|
|
16115
|
+
const lightUrls = p(manifest, "light");
|
|
16116
|
+
if (lightUrls.length > 0) {
|
|
16117
|
+
overlays.push({
|
|
16118
|
+
spriteUrl: lightUrls[0],
|
|
16119
|
+
originX,
|
|
16120
|
+
originY,
|
|
16121
|
+
alpha: 0.6,
|
|
16122
|
+
fadeRate: -0.4,
|
|
16123
|
+
pulseAmplitude: 0.25,
|
|
16124
|
+
pulseFrequency: 2.5,
|
|
16125
|
+
scale: 0.6,
|
|
16126
|
+
blendMode: "lighter",
|
|
16127
|
+
lifetime: 1500
|
|
16128
|
+
});
|
|
16129
|
+
}
|
|
16130
|
+
return {
|
|
16131
|
+
particles: particles.filter((pc) => pc.spriteUrls.length > 0),
|
|
16132
|
+
sequences: [],
|
|
16133
|
+
overlays,
|
|
16134
|
+
screenShake: 0,
|
|
16135
|
+
screenFlash: null
|
|
16136
|
+
};
|
|
16137
|
+
},
|
|
16138
|
+
// =====================================================================
|
|
16139
|
+
// DEFEND / SHIELD — star (blue) + circle (blue, pulse)
|
|
16140
|
+
// =====================================================================
|
|
16141
|
+
defend: (originX, originY) => {
|
|
16142
|
+
const particles = [
|
|
16143
|
+
{
|
|
16144
|
+
spriteUrls: p(manifest, "star"),
|
|
16145
|
+
count: 8,
|
|
16146
|
+
originX,
|
|
16147
|
+
originY,
|
|
16148
|
+
spread: 18,
|
|
16149
|
+
velocityMin: 10,
|
|
16150
|
+
velocityMax: 35,
|
|
16151
|
+
angleMin: 0,
|
|
16152
|
+
angleMax: PI * 2,
|
|
16153
|
+
gravity: 0,
|
|
16154
|
+
tint: { r: 80, g: 160, b: 255 },
|
|
16155
|
+
scaleMin: 0.12,
|
|
16156
|
+
scaleMax: 0.25,
|
|
16157
|
+
lifetimeMin: 600,
|
|
16158
|
+
lifetimeMax: 1e3,
|
|
16159
|
+
fadeRate: -0.8,
|
|
16160
|
+
blendMode: "lighter",
|
|
16161
|
+
rotationSpeedMin: -1,
|
|
16162
|
+
rotationSpeedMax: 1
|
|
16163
|
+
}
|
|
16164
|
+
];
|
|
16165
|
+
const overlays = [];
|
|
16166
|
+
const circleUrls = p(manifest, "circle");
|
|
16167
|
+
if (circleUrls.length > 0) {
|
|
16168
|
+
overlays.push({
|
|
16169
|
+
spriteUrl: circleUrls[0],
|
|
16170
|
+
originX,
|
|
16171
|
+
originY,
|
|
16172
|
+
alpha: 0.6,
|
|
16173
|
+
fadeRate: -0.3,
|
|
16174
|
+
pulseAmplitude: 0.2,
|
|
16175
|
+
pulseFrequency: 2,
|
|
16176
|
+
scale: 0.6,
|
|
16177
|
+
blendMode: "lighter",
|
|
16178
|
+
lifetime: 1500
|
|
16179
|
+
});
|
|
16180
|
+
}
|
|
16181
|
+
return {
|
|
16182
|
+
particles: particles.filter((pc) => pc.spriteUrls.length > 0),
|
|
16183
|
+
sequences: [],
|
|
16184
|
+
overlays,
|
|
16185
|
+
screenShake: 0,
|
|
16186
|
+
screenFlash: null
|
|
16187
|
+
};
|
|
16188
|
+
},
|
|
16189
|
+
// shield aliases to defend
|
|
16190
|
+
shield: (originX, originY) => {
|
|
16191
|
+
const particles = [
|
|
16192
|
+
{
|
|
16193
|
+
spriteUrls: p(manifest, "star"),
|
|
16194
|
+
count: 10,
|
|
16195
|
+
originX,
|
|
16196
|
+
originY,
|
|
16197
|
+
spread: 20,
|
|
16198
|
+
velocityMin: 8,
|
|
16199
|
+
velocityMax: 30,
|
|
16200
|
+
angleMin: 0,
|
|
16201
|
+
angleMax: PI * 2,
|
|
16202
|
+
gravity: 0,
|
|
16203
|
+
tint: { r: 60, g: 180, b: 255 },
|
|
16204
|
+
scaleMin: 0.1,
|
|
16205
|
+
scaleMax: 0.22,
|
|
16206
|
+
lifetimeMin: 700,
|
|
16207
|
+
lifetimeMax: 1200,
|
|
16208
|
+
fadeRate: -0.7,
|
|
16209
|
+
blendMode: "lighter",
|
|
16210
|
+
rotationSpeedMin: -0.8,
|
|
16211
|
+
rotationSpeedMax: 0.8
|
|
16212
|
+
}
|
|
16213
|
+
];
|
|
16214
|
+
const overlays = [];
|
|
16215
|
+
const circleUrls = p(manifest, "circle");
|
|
16216
|
+
if (circleUrls.length > 0) {
|
|
16217
|
+
overlays.push({
|
|
16218
|
+
spriteUrl: circleUrls[0],
|
|
16219
|
+
originX,
|
|
16220
|
+
originY,
|
|
16221
|
+
alpha: 0.7,
|
|
16222
|
+
fadeRate: -0.25,
|
|
16223
|
+
pulseAmplitude: 0.25,
|
|
16224
|
+
pulseFrequency: 1.8,
|
|
16225
|
+
scale: 0.7,
|
|
16226
|
+
blendMode: "lighter",
|
|
16227
|
+
lifetime: 1800
|
|
16228
|
+
});
|
|
16229
|
+
}
|
|
16230
|
+
return {
|
|
16231
|
+
particles: particles.filter((pc) => pc.spriteUrls.length > 0),
|
|
16232
|
+
sequences: [],
|
|
16233
|
+
overlays,
|
|
16234
|
+
screenShake: 0,
|
|
16235
|
+
screenFlash: null
|
|
16236
|
+
};
|
|
16237
|
+
},
|
|
16238
|
+
// =====================================================================
|
|
16239
|
+
// HIT — spark (orange) + flash (5 frames) + screen shake/flash
|
|
16240
|
+
// =====================================================================
|
|
16241
|
+
hit: (originX, originY) => {
|
|
16242
|
+
const particles = [
|
|
16243
|
+
{
|
|
16244
|
+
spriteUrls: p(manifest, "spark"),
|
|
16245
|
+
count: 10,
|
|
16246
|
+
originX,
|
|
16247
|
+
originY,
|
|
16248
|
+
spread: 8,
|
|
16249
|
+
velocityMin: 50,
|
|
16250
|
+
velocityMax: 150,
|
|
16251
|
+
angleMin: 0,
|
|
16252
|
+
angleMax: PI * 2,
|
|
16253
|
+
gravity: 80,
|
|
16254
|
+
tint: { r: 255, g: 180, b: 50 },
|
|
16255
|
+
scaleMin: 0.08,
|
|
16256
|
+
scaleMax: 0.2,
|
|
16257
|
+
lifetimeMin: 200,
|
|
16258
|
+
lifetimeMax: 500,
|
|
16259
|
+
fadeRate: -2.5
|
|
16260
|
+
}
|
|
16261
|
+
];
|
|
16262
|
+
const sequences = [];
|
|
16263
|
+
const flashFrames = anim(manifest, "flash");
|
|
16264
|
+
if (flashFrames.length > 0) {
|
|
16265
|
+
sequences.push({
|
|
16266
|
+
frameUrls: flashFrames.slice(0, 5),
|
|
16267
|
+
originX,
|
|
16268
|
+
originY,
|
|
16269
|
+
frameDuration: 40,
|
|
16270
|
+
scale: 0.3
|
|
16271
|
+
});
|
|
16272
|
+
}
|
|
16273
|
+
return {
|
|
16274
|
+
particles: particles.filter((pc) => pc.spriteUrls.length > 0),
|
|
16275
|
+
sequences,
|
|
16276
|
+
overlays: [],
|
|
16277
|
+
screenShake: 3,
|
|
16278
|
+
screenFlash: { r: 255, g: 50, b: 50, duration: 150 }
|
|
16279
|
+
};
|
|
16280
|
+
},
|
|
16281
|
+
// critical aliases to hit with bigger shake
|
|
16282
|
+
critical: (originX, originY) => {
|
|
16283
|
+
const particles = [
|
|
16284
|
+
{
|
|
16285
|
+
spriteUrls: p(manifest, "flame"),
|
|
16286
|
+
count: 8,
|
|
16287
|
+
originX,
|
|
16288
|
+
originY,
|
|
16289
|
+
spread: 12,
|
|
16290
|
+
velocityMin: 60,
|
|
16291
|
+
velocityMax: 180,
|
|
16292
|
+
angleMin: 0,
|
|
16293
|
+
angleMax: PI * 2,
|
|
16294
|
+
gravity: 60,
|
|
16295
|
+
tint: { r: 255, g: 120, b: 30 },
|
|
16296
|
+
scaleMin: 0.15,
|
|
16297
|
+
scaleMax: 0.4,
|
|
16298
|
+
lifetimeMin: 300,
|
|
16299
|
+
lifetimeMax: 600,
|
|
16300
|
+
fadeRate: -2
|
|
16301
|
+
},
|
|
16302
|
+
{
|
|
16303
|
+
spriteUrls: p(manifest, "spark"),
|
|
16304
|
+
count: 12,
|
|
16305
|
+
originX,
|
|
16306
|
+
originY,
|
|
16307
|
+
spread: 10,
|
|
16308
|
+
velocityMin: 80,
|
|
16309
|
+
velocityMax: 200,
|
|
16310
|
+
angleMin: 0,
|
|
16311
|
+
angleMax: PI * 2,
|
|
16312
|
+
gravity: 100,
|
|
16313
|
+
tint: { r: 255, g: 200, b: 60 },
|
|
16314
|
+
scaleMin: 0.06,
|
|
16315
|
+
scaleMax: 0.18,
|
|
16316
|
+
lifetimeMin: 200,
|
|
16317
|
+
lifetimeMax: 400,
|
|
16318
|
+
fadeRate: -3
|
|
16319
|
+
}
|
|
16320
|
+
];
|
|
16321
|
+
const sequences = [];
|
|
16322
|
+
const flashFrames = anim(manifest, "flash");
|
|
16323
|
+
if (flashFrames.length > 0) {
|
|
16324
|
+
sequences.push({
|
|
16325
|
+
frameUrls: flashFrames,
|
|
16326
|
+
originX,
|
|
16327
|
+
originY,
|
|
16328
|
+
frameDuration: 30,
|
|
16329
|
+
scale: 0.5
|
|
16330
|
+
});
|
|
16331
|
+
}
|
|
16332
|
+
return {
|
|
16333
|
+
particles: particles.filter((pc) => pc.spriteUrls.length > 0),
|
|
16334
|
+
sequences,
|
|
16335
|
+
overlays: [],
|
|
16336
|
+
screenShake: 6,
|
|
16337
|
+
screenFlash: { r: 255, g: 80, b: 0, duration: 200 }
|
|
16338
|
+
};
|
|
16339
|
+
},
|
|
16340
|
+
// =====================================================================
|
|
16341
|
+
// DEATH — dirt (gray) + explosion + black smoke + scorch (ground)
|
|
16342
|
+
// =====================================================================
|
|
16343
|
+
death: (originX, originY) => {
|
|
16344
|
+
const particles = [
|
|
16345
|
+
{
|
|
16346
|
+
spriteUrls: p(manifest, "dirt"),
|
|
16347
|
+
count: 8,
|
|
16348
|
+
originX,
|
|
16349
|
+
originY,
|
|
16350
|
+
spread: 10,
|
|
16351
|
+
velocityMin: 30,
|
|
16352
|
+
velocityMax: 100,
|
|
16353
|
+
angleMin: 0,
|
|
16354
|
+
angleMax: PI * 2,
|
|
16355
|
+
gravity: 100,
|
|
16356
|
+
tint: { r: 140, g: 140, b: 140 },
|
|
16357
|
+
scaleMin: 0.15,
|
|
16358
|
+
scaleMax: 0.35,
|
|
16359
|
+
lifetimeMin: 500,
|
|
16360
|
+
lifetimeMax: 900,
|
|
16361
|
+
fadeRate: -1.2
|
|
16362
|
+
}
|
|
16363
|
+
];
|
|
16364
|
+
const sequences = [];
|
|
16365
|
+
const explosionFrames = anim(manifest, "explosion");
|
|
16366
|
+
if (explosionFrames.length > 0) {
|
|
16367
|
+
sequences.push({
|
|
16368
|
+
frameUrls: explosionFrames,
|
|
16369
|
+
originX,
|
|
16370
|
+
originY,
|
|
16371
|
+
frameDuration: 60,
|
|
16372
|
+
scale: 0.5
|
|
16373
|
+
});
|
|
16374
|
+
}
|
|
16375
|
+
const blackSmokeFrames = anim(manifest, "blackSmoke");
|
|
16376
|
+
if (blackSmokeFrames.length > 0) {
|
|
16377
|
+
sequences.push({
|
|
16378
|
+
frameUrls: blackSmokeFrames,
|
|
16379
|
+
originX,
|
|
16380
|
+
originY: originY - 10,
|
|
16381
|
+
frameDuration: 50,
|
|
16382
|
+
scale: 0.4,
|
|
16383
|
+
alpha: 0.7
|
|
16384
|
+
});
|
|
16385
|
+
}
|
|
16386
|
+
const overlays = [];
|
|
16387
|
+
const scorchUrls = p(manifest, "scorch");
|
|
16388
|
+
if (scorchUrls.length > 0) {
|
|
16389
|
+
overlays.push({
|
|
16390
|
+
spriteUrl: scorchUrls[0],
|
|
16391
|
+
originX,
|
|
16392
|
+
originY: originY + 10,
|
|
16393
|
+
alpha: 0.6,
|
|
16394
|
+
fadeRate: -0.15,
|
|
16395
|
+
scale: 0.4,
|
|
16396
|
+
lifetime: 4e3
|
|
16397
|
+
});
|
|
16398
|
+
}
|
|
16399
|
+
return {
|
|
16400
|
+
particles: particles.filter((pc) => pc.spriteUrls.length > 0),
|
|
16401
|
+
sequences,
|
|
16402
|
+
overlays,
|
|
16403
|
+
screenShake: 0,
|
|
16404
|
+
screenFlash: null
|
|
16405
|
+
};
|
|
16406
|
+
},
|
|
16407
|
+
// =====================================================================
|
|
16408
|
+
// BUFF — star (gold) + symbol + flare (gold, pulse)
|
|
16409
|
+
// =====================================================================
|
|
16410
|
+
buff: (originX, originY) => {
|
|
16411
|
+
const particles = [
|
|
16412
|
+
{
|
|
16413
|
+
spriteUrls: p(manifest, "star"),
|
|
16414
|
+
count: 6,
|
|
16415
|
+
originX,
|
|
16416
|
+
originY,
|
|
16417
|
+
spread: 15,
|
|
16418
|
+
velocityMin: 15,
|
|
16419
|
+
velocityMax: 50,
|
|
16420
|
+
angleMin: -PI,
|
|
16421
|
+
angleMax: 0,
|
|
16422
|
+
gravity: -30,
|
|
16423
|
+
tint: { r: 255, g: 215, b: 50 },
|
|
16424
|
+
scaleMin: 0.12,
|
|
16425
|
+
scaleMax: 0.25,
|
|
16426
|
+
lifetimeMin: 600,
|
|
16427
|
+
lifetimeMax: 1e3,
|
|
16428
|
+
fadeRate: -0.8,
|
|
16429
|
+
blendMode: "lighter"
|
|
16430
|
+
},
|
|
16431
|
+
{
|
|
16432
|
+
spriteUrls: p(manifest, "symbol"),
|
|
16433
|
+
count: 2,
|
|
16434
|
+
originX,
|
|
16435
|
+
originY: originY - 10,
|
|
16436
|
+
spread: 8,
|
|
16437
|
+
velocityMin: 5,
|
|
16438
|
+
velocityMax: 20,
|
|
16439
|
+
angleMin: -PI * 0.7,
|
|
16440
|
+
angleMax: -PI * 0.3,
|
|
16441
|
+
gravity: -20,
|
|
16442
|
+
tint: { r: 255, g: 230, b: 100 },
|
|
16443
|
+
scaleMin: 0.2,
|
|
16444
|
+
scaleMax: 0.35,
|
|
16445
|
+
lifetimeMin: 800,
|
|
16446
|
+
lifetimeMax: 1200,
|
|
16447
|
+
fadeRate: -0.6,
|
|
16448
|
+
blendMode: "lighter"
|
|
16449
|
+
}
|
|
16450
|
+
];
|
|
16451
|
+
const overlays = [];
|
|
16452
|
+
const flareUrls = p(manifest, "flare");
|
|
16453
|
+
if (flareUrls.length > 0) {
|
|
16454
|
+
overlays.push({
|
|
16455
|
+
spriteUrl: flareUrls[0],
|
|
16456
|
+
originX,
|
|
16457
|
+
originY,
|
|
16458
|
+
alpha: 0.5,
|
|
16459
|
+
fadeRate: -0.3,
|
|
16460
|
+
pulseAmplitude: 0.3,
|
|
16461
|
+
pulseFrequency: 2,
|
|
16462
|
+
scale: 0.5,
|
|
16463
|
+
blendMode: "lighter",
|
|
16464
|
+
lifetime: 1500
|
|
16465
|
+
});
|
|
16466
|
+
}
|
|
16467
|
+
return {
|
|
16468
|
+
particles: particles.filter((pc) => pc.spriteUrls.length > 0),
|
|
16469
|
+
sequences: [],
|
|
16470
|
+
overlays,
|
|
16471
|
+
screenShake: 0,
|
|
16472
|
+
screenFlash: null
|
|
16473
|
+
};
|
|
16474
|
+
},
|
|
16475
|
+
// =====================================================================
|
|
16476
|
+
// DEBUFF — scorch (dark) + smoke (purple tint)
|
|
16477
|
+
// =====================================================================
|
|
16478
|
+
debuff: (originX, originY) => {
|
|
16479
|
+
const particles = [
|
|
16480
|
+
{
|
|
16481
|
+
spriteUrls: p(manifest, "scorch"),
|
|
16482
|
+
count: 4,
|
|
16483
|
+
originX,
|
|
16484
|
+
originY,
|
|
16485
|
+
spread: 12,
|
|
16486
|
+
velocityMin: 15,
|
|
16487
|
+
velocityMax: 40,
|
|
16488
|
+
angleMin: -PI,
|
|
16489
|
+
angleMax: 0,
|
|
16490
|
+
gravity: -20,
|
|
16491
|
+
tint: { r: 120, g: 40, b: 160 },
|
|
16492
|
+
scaleMin: 0.15,
|
|
16493
|
+
scaleMax: 0.3,
|
|
16494
|
+
lifetimeMin: 500,
|
|
16495
|
+
lifetimeMax: 800,
|
|
16496
|
+
fadeRate: -1
|
|
16497
|
+
},
|
|
16498
|
+
{
|
|
16499
|
+
spriteUrls: p(manifest, "smoke").slice(0, 3),
|
|
16500
|
+
count: 3,
|
|
16501
|
+
originX,
|
|
16502
|
+
originY,
|
|
16503
|
+
spread: 10,
|
|
16504
|
+
velocityMin: 8,
|
|
16505
|
+
velocityMax: 25,
|
|
16506
|
+
angleMin: -PI * 0.8,
|
|
16507
|
+
angleMax: -PI * 0.2,
|
|
16508
|
+
gravity: -15,
|
|
16509
|
+
tint: { r: 100, g: 50, b: 140 },
|
|
16510
|
+
scaleMin: 0.2,
|
|
16511
|
+
scaleMax: 0.35,
|
|
16512
|
+
lifetimeMin: 600,
|
|
16513
|
+
lifetimeMax: 1e3,
|
|
16514
|
+
fadeRate: -0.8
|
|
16515
|
+
}
|
|
16516
|
+
];
|
|
16517
|
+
const overlays = [];
|
|
16518
|
+
const circleUrls = p(manifest, "circle");
|
|
16519
|
+
if (circleUrls.length > 0) {
|
|
16520
|
+
overlays.push({
|
|
16521
|
+
spriteUrl: circleUrls[0],
|
|
16522
|
+
originX,
|
|
16523
|
+
originY,
|
|
16524
|
+
alpha: 0.4,
|
|
16525
|
+
fadeRate: -0.4,
|
|
16526
|
+
pulseAmplitude: 0.15,
|
|
16527
|
+
pulseFrequency: 2,
|
|
16528
|
+
scale: 0.45,
|
|
16529
|
+
lifetime: 1200
|
|
16530
|
+
});
|
|
16531
|
+
}
|
|
16532
|
+
return {
|
|
16533
|
+
particles: particles.filter((pc) => pc.spriteUrls.length > 0),
|
|
16534
|
+
sequences: [],
|
|
16535
|
+
overlays,
|
|
16536
|
+
screenShake: 0,
|
|
16537
|
+
screenFlash: null
|
|
16538
|
+
};
|
|
16539
|
+
},
|
|
16540
|
+
// =====================================================================
|
|
16541
|
+
// AOE — explosion (large) + flame + spark (radial) + screen shake
|
|
16542
|
+
// =====================================================================
|
|
16543
|
+
aoe: (originX, originY) => {
|
|
16544
|
+
const particles = [
|
|
16545
|
+
{
|
|
16546
|
+
spriteUrls: p(manifest, "flame"),
|
|
16547
|
+
count: 10,
|
|
16548
|
+
originX,
|
|
16549
|
+
originY,
|
|
16550
|
+
spread: 20,
|
|
16551
|
+
velocityMin: 40,
|
|
16552
|
+
velocityMax: 140,
|
|
16553
|
+
angleMin: 0,
|
|
16554
|
+
angleMax: PI * 2,
|
|
16555
|
+
gravity: 40,
|
|
16556
|
+
tint: { r: 255, g: 140, b: 30 },
|
|
16557
|
+
scaleMin: 0.2,
|
|
16558
|
+
scaleMax: 0.5,
|
|
16559
|
+
lifetimeMin: 400,
|
|
16560
|
+
lifetimeMax: 800,
|
|
16561
|
+
fadeRate: -1.5
|
|
16562
|
+
},
|
|
16563
|
+
{
|
|
16564
|
+
spriteUrls: p(manifest, "spark"),
|
|
16565
|
+
count: 15,
|
|
16566
|
+
originX,
|
|
16567
|
+
originY,
|
|
16568
|
+
spread: 15,
|
|
16569
|
+
velocityMin: 60,
|
|
16570
|
+
velocityMax: 200,
|
|
16571
|
+
angleMin: 0,
|
|
16572
|
+
angleMax: PI * 2,
|
|
16573
|
+
gravity: 60,
|
|
16574
|
+
tint: { r: 255, g: 180, b: 60 },
|
|
16575
|
+
scaleMin: 0.06,
|
|
16576
|
+
scaleMax: 0.15,
|
|
16577
|
+
lifetimeMin: 200,
|
|
16578
|
+
lifetimeMax: 500,
|
|
16579
|
+
fadeRate: -2.5
|
|
16580
|
+
}
|
|
16581
|
+
];
|
|
16582
|
+
const sequences = [];
|
|
16583
|
+
const explosionFrames = anim(manifest, "explosion");
|
|
16584
|
+
if (explosionFrames.length > 0) {
|
|
16585
|
+
sequences.push({
|
|
16586
|
+
frameUrls: explosionFrames,
|
|
16587
|
+
originX,
|
|
16588
|
+
originY,
|
|
16589
|
+
frameDuration: 50,
|
|
16590
|
+
scale: 0.6
|
|
16591
|
+
});
|
|
16592
|
+
}
|
|
16593
|
+
return {
|
|
16594
|
+
particles: particles.filter((pc) => pc.spriteUrls.length > 0),
|
|
16595
|
+
sequences,
|
|
16596
|
+
overlays: [],
|
|
16597
|
+
screenShake: 5,
|
|
16598
|
+
screenFlash: { r: 255, g: 160, b: 0, duration: 180 }
|
|
16599
|
+
};
|
|
16600
|
+
}
|
|
16601
|
+
};
|
|
16602
|
+
}
|
|
16603
|
+
var ACTION_EMOJI = {
|
|
16604
|
+
melee: { emoji: "\u2694\uFE0F", color: "var(--color-error)", label: "Slash" },
|
|
16605
|
+
ranged: { emoji: "\u{1F3F9}", color: "var(--color-warning)", label: "Arrow" },
|
|
16606
|
+
magic: { emoji: "\u2728", color: "var(--color-primary)", label: "Spell" },
|
|
16607
|
+
heal: { emoji: "\u{1F49A}", color: "var(--color-success)", label: "Heal" },
|
|
16608
|
+
buff: { emoji: "\u2B06\uFE0F", color: "var(--color-info)", label: "Buff" },
|
|
16609
|
+
debuff: { emoji: "\u2B07\uFE0F", color: "var(--color-warning)", label: "Debuff" },
|
|
16610
|
+
shield: { emoji: "\u{1F6E1}\uFE0F", color: "var(--color-info)", label: "Shield" },
|
|
16611
|
+
aoe: { emoji: "\u{1F4A5}", color: "var(--color-error)", label: "Explosion" },
|
|
16612
|
+
critical: { emoji: "\u{1F525}", color: "var(--color-error)", label: "Critical" },
|
|
16613
|
+
defend: { emoji: "\u{1F6E1}\uFE0F", color: "var(--color-info)", label: "Defend" },
|
|
16614
|
+
hit: { emoji: "\u{1F4A5}", color: "var(--color-error)", label: "Hit" },
|
|
16615
|
+
death: { emoji: "\u{1F480}", color: "var(--color-error)", label: "Death" }
|
|
16616
|
+
};
|
|
16617
|
+
function CanvasEffectEngine({
|
|
16618
|
+
actionType,
|
|
16619
|
+
x,
|
|
16620
|
+
y,
|
|
16621
|
+
duration = 2e3,
|
|
16622
|
+
intensity = 1,
|
|
16623
|
+
onComplete,
|
|
16624
|
+
className,
|
|
16625
|
+
assetManifest,
|
|
16626
|
+
width = 400,
|
|
16627
|
+
height = 300
|
|
16628
|
+
}) {
|
|
16629
|
+
const canvasRef = useRef(null);
|
|
16630
|
+
const stateRef = useRef({ ...EMPTY_EFFECT_STATE });
|
|
16631
|
+
const lastTimeRef = useRef(0);
|
|
16632
|
+
const rafRef = useRef(0);
|
|
16633
|
+
const imageCacheRef = useRef(/* @__PURE__ */ new Map());
|
|
16634
|
+
const [shakeOffset, setShakeOffset] = useState({ x: 0, y: 0 });
|
|
16635
|
+
const [flash, setFlash] = useState(null);
|
|
16636
|
+
const shakeRef = useRef({ x: 0, y: 0, intensity: 0 });
|
|
16637
|
+
const presets = useMemo(() => createCombatPresets(assetManifest), [assetManifest]);
|
|
16638
|
+
const spriteUrls = useMemo(() => getAllEffectSpriteUrls(assetManifest), [assetManifest]);
|
|
16639
|
+
useEffect(() => {
|
|
16640
|
+
const cache = imageCacheRef.current;
|
|
16641
|
+
for (const url of spriteUrls) {
|
|
16642
|
+
if (!cache.has(url)) {
|
|
16643
|
+
const img = new Image();
|
|
16644
|
+
img.crossOrigin = "anonymous";
|
|
16645
|
+
img.src = url;
|
|
16646
|
+
cache.set(url, img);
|
|
16647
|
+
}
|
|
16648
|
+
}
|
|
16649
|
+
}, [spriteUrls]);
|
|
16650
|
+
const getImage = useCallback((url) => {
|
|
16651
|
+
const img = imageCacheRef.current.get(url);
|
|
16652
|
+
return img?.complete ? img : void 0;
|
|
16653
|
+
}, []);
|
|
16654
|
+
useEffect(() => {
|
|
16655
|
+
const now = performance.now();
|
|
16656
|
+
const effectX = x || width / 2;
|
|
16657
|
+
const effectY = y || height / 2;
|
|
16658
|
+
const preset = presets[actionType](effectX, effectY);
|
|
16659
|
+
const state = stateRef.current;
|
|
16660
|
+
for (const emitter of preset.particles) {
|
|
16661
|
+
const scaledEmitter = { ...emitter, count: Math.round(emitter.count * intensity) };
|
|
16662
|
+
state.particles.push(...spawnParticles(scaledEmitter, now));
|
|
16663
|
+
}
|
|
16664
|
+
for (const seqConfig of preset.sequences) {
|
|
16665
|
+
state.sequences.push(spawnSequence(seqConfig, now));
|
|
16666
|
+
}
|
|
16667
|
+
for (const ovConfig of preset.overlays) {
|
|
16668
|
+
state.overlays.push(spawnOverlay(ovConfig, now));
|
|
16669
|
+
}
|
|
16670
|
+
if (preset.screenShake > 0) {
|
|
16671
|
+
shakeRef.current.intensity = preset.screenShake * intensity;
|
|
16672
|
+
}
|
|
16673
|
+
if (preset.screenFlash) {
|
|
16674
|
+
const { r, g, b, duration: flashDur } = preset.screenFlash;
|
|
16675
|
+
setFlash({ color: `rgb(${r}, ${g}, ${b})`, alpha: 0.3 });
|
|
16676
|
+
setTimeout(() => setFlash(null), flashDur);
|
|
16677
|
+
}
|
|
16678
|
+
const timer = setTimeout(() => {
|
|
16679
|
+
onComplete?.();
|
|
16680
|
+
}, duration);
|
|
16681
|
+
return () => clearTimeout(timer);
|
|
16682
|
+
}, []);
|
|
16683
|
+
useEffect(() => {
|
|
16684
|
+
const canvas = canvasRef.current;
|
|
16685
|
+
if (!canvas) return;
|
|
16686
|
+
const ctx = canvas.getContext("2d");
|
|
16687
|
+
if (!ctx) return;
|
|
16688
|
+
function loop(animTime) {
|
|
16689
|
+
const delta = lastTimeRef.current > 0 ? animTime - lastTimeRef.current : 16;
|
|
16690
|
+
lastTimeRef.current = animTime;
|
|
16691
|
+
stateRef.current = updateEffectState(stateRef.current, animTime, delta);
|
|
16692
|
+
if (shakeRef.current.intensity > 0.2) {
|
|
16693
|
+
const i = shakeRef.current.intensity;
|
|
16694
|
+
shakeRef.current.x = (Math.random() - 0.5) * i * 2;
|
|
16695
|
+
shakeRef.current.y = (Math.random() - 0.5) * i * 2;
|
|
16696
|
+
shakeRef.current.intensity *= 0.85;
|
|
16697
|
+
setShakeOffset({ x: shakeRef.current.x, y: shakeRef.current.y });
|
|
16698
|
+
} else if (shakeRef.current.intensity > 0) {
|
|
16699
|
+
shakeRef.current = { x: 0, y: 0, intensity: 0 };
|
|
16700
|
+
setShakeOffset({ x: 0, y: 0 });
|
|
16701
|
+
}
|
|
16702
|
+
ctx.clearRect(0, 0, width, height);
|
|
16703
|
+
drawEffectState(ctx, stateRef.current, animTime, getImage);
|
|
16704
|
+
if (hasActiveEffects(stateRef.current)) {
|
|
16705
|
+
rafRef.current = requestAnimationFrame(loop);
|
|
16706
|
+
}
|
|
16707
|
+
}
|
|
16708
|
+
rafRef.current = requestAnimationFrame(loop);
|
|
16709
|
+
return () => cancelAnimationFrame(rafRef.current);
|
|
16710
|
+
}, [width, height, getImage]);
|
|
16711
|
+
const shakeStyle = shakeOffset.x !== 0 || shakeOffset.y !== 0 ? { transform: `translate(${shakeOffset.x}px, ${shakeOffset.y}px)` } : {};
|
|
16712
|
+
return /* @__PURE__ */ jsxs(
|
|
16713
|
+
Box,
|
|
16714
|
+
{
|
|
16715
|
+
className: cn("absolute inset-0 pointer-events-none z-10", className),
|
|
16716
|
+
style: shakeStyle,
|
|
16717
|
+
children: [
|
|
16718
|
+
flash && /* @__PURE__ */ jsx(
|
|
16719
|
+
Box,
|
|
16720
|
+
{
|
|
16721
|
+
className: "absolute inset-0 z-20 pointer-events-none rounded-lg",
|
|
16722
|
+
style: { backgroundColor: flash.color, opacity: flash.alpha }
|
|
16723
|
+
}
|
|
16724
|
+
),
|
|
16725
|
+
/* @__PURE__ */ jsx(
|
|
16726
|
+
"canvas",
|
|
16727
|
+
{
|
|
16728
|
+
ref: canvasRef,
|
|
16729
|
+
width,
|
|
16730
|
+
height,
|
|
16731
|
+
className: "absolute inset-0 w-full h-full",
|
|
16732
|
+
style: { imageRendering: "pixelated" }
|
|
16733
|
+
}
|
|
16734
|
+
)
|
|
16735
|
+
]
|
|
16736
|
+
}
|
|
16737
|
+
);
|
|
16738
|
+
}
|
|
16739
|
+
function EmojiEffect({
|
|
16740
|
+
actionType,
|
|
16741
|
+
x,
|
|
16742
|
+
y,
|
|
16743
|
+
duration = 800,
|
|
16744
|
+
intensity = 1,
|
|
16745
|
+
onComplete,
|
|
16746
|
+
className,
|
|
16747
|
+
effectSpriteUrl,
|
|
16748
|
+
assetBaseUrl
|
|
16749
|
+
}) {
|
|
16750
|
+
const [visible, setVisible] = useState(true);
|
|
16751
|
+
const [phase, setPhase] = useState("enter");
|
|
16752
|
+
useEffect(() => {
|
|
16753
|
+
const enterTimer = setTimeout(() => setPhase("active"), 100);
|
|
16754
|
+
const exitTimer = setTimeout(() => setPhase("exit"), duration * 0.7);
|
|
16755
|
+
const doneTimer = setTimeout(() => {
|
|
16756
|
+
setVisible(false);
|
|
16757
|
+
onComplete?.();
|
|
16758
|
+
}, duration);
|
|
16759
|
+
return () => {
|
|
16760
|
+
clearTimeout(enterTimer);
|
|
16761
|
+
clearTimeout(exitTimer);
|
|
16762
|
+
clearTimeout(doneTimer);
|
|
16763
|
+
};
|
|
16764
|
+
}, [duration, onComplete]);
|
|
16765
|
+
if (!visible) return null;
|
|
16766
|
+
const config = ACTION_EMOJI[actionType] ?? ACTION_EMOJI.melee;
|
|
16767
|
+
const scaleVal = phase === "enter" ? 0.3 : phase === "active" ? intensity : 0.5;
|
|
16768
|
+
const opacity = phase === "exit" ? 0 : 1;
|
|
16769
|
+
const resolvedSpriteUrl = effectSpriteUrl ? effectSpriteUrl.startsWith("http") || effectSpriteUrl.startsWith("/") ? effectSpriteUrl : assetBaseUrl ? `${assetBaseUrl.replace(/\/$/, "")}/${effectSpriteUrl}` : effectSpriteUrl : void 0;
|
|
16770
|
+
return /* @__PURE__ */ jsxs(
|
|
16771
|
+
Box,
|
|
16772
|
+
{
|
|
16773
|
+
className: cn(
|
|
16774
|
+
"fixed pointer-events-none z-50 flex items-center justify-center",
|
|
16775
|
+
"transition-all ease-out",
|
|
16776
|
+
className
|
|
16777
|
+
),
|
|
16778
|
+
style: {
|
|
16779
|
+
left: x,
|
|
16780
|
+
top: y,
|
|
16781
|
+
transform: `translate(-50%, -50%) scale(${scaleVal})`,
|
|
16782
|
+
opacity,
|
|
16783
|
+
transitionDuration: phase === "enter" ? "100ms" : "300ms"
|
|
16784
|
+
},
|
|
16785
|
+
children: [
|
|
16786
|
+
/* @__PURE__ */ jsx(
|
|
16787
|
+
Box,
|
|
16788
|
+
{
|
|
16789
|
+
className: "absolute rounded-full animate-ping",
|
|
16790
|
+
style: {
|
|
16791
|
+
width: 48 * intensity,
|
|
16792
|
+
height: 48 * intensity,
|
|
16793
|
+
backgroundColor: config.color,
|
|
16794
|
+
opacity: 0.25
|
|
16795
|
+
}
|
|
16796
|
+
}
|
|
16797
|
+
),
|
|
16798
|
+
resolvedSpriteUrl ? /* @__PURE__ */ jsx(
|
|
16799
|
+
"img",
|
|
16800
|
+
{
|
|
16801
|
+
src: resolvedSpriteUrl,
|
|
16802
|
+
alt: config.label,
|
|
16803
|
+
className: "relative drop-shadow-lg",
|
|
16804
|
+
style: {
|
|
16805
|
+
width: `${3 * intensity}rem`,
|
|
16806
|
+
height: `${3 * intensity}rem`,
|
|
16807
|
+
objectFit: "contain",
|
|
16808
|
+
imageRendering: "pixelated"
|
|
16809
|
+
}
|
|
16810
|
+
}
|
|
16811
|
+
) : /* @__PURE__ */ jsx(
|
|
16812
|
+
"span",
|
|
16813
|
+
{
|
|
16814
|
+
className: "relative text-3xl drop-shadow-lg",
|
|
16815
|
+
style: { fontSize: `${2 * intensity}rem` },
|
|
16816
|
+
role: "img",
|
|
16817
|
+
"aria-label": config.label,
|
|
16818
|
+
children: config.emoji
|
|
16819
|
+
}
|
|
16820
|
+
)
|
|
16821
|
+
]
|
|
16822
|
+
}
|
|
16823
|
+
);
|
|
16824
|
+
}
|
|
16825
|
+
function CanvasEffect(props) {
|
|
16826
|
+
const eventBus = useEventBus();
|
|
16827
|
+
const { completeEvent, onComplete, ...rest } = props;
|
|
16828
|
+
const handleComplete = useCallback(() => {
|
|
16829
|
+
if (completeEvent) eventBus.emit(`UI:${completeEvent}`, {});
|
|
16830
|
+
onComplete?.();
|
|
16831
|
+
}, [completeEvent, eventBus, onComplete]);
|
|
16832
|
+
const enhancedProps = { ...rest, onComplete: handleComplete };
|
|
16833
|
+
if (props.assetManifest) {
|
|
16834
|
+
return /* @__PURE__ */ jsx(CanvasEffectEngine, { ...enhancedProps, assetManifest: props.assetManifest });
|
|
16835
|
+
}
|
|
16836
|
+
return /* @__PURE__ */ jsx(EmojiEffect, { ...enhancedProps });
|
|
16837
|
+
}
|
|
16838
|
+
CanvasEffect.displayName = "CanvasEffect";
|
|
15595
16839
|
function VStackPattern({
|
|
15596
16840
|
gap = "md",
|
|
15597
16841
|
align = "stretch",
|
|
@@ -15616,7 +16860,7 @@ function HStackPattern({
|
|
|
15616
16860
|
}
|
|
15617
16861
|
HStackPattern.displayName = "HStackPattern";
|
|
15618
16862
|
function BoxPattern({
|
|
15619
|
-
p,
|
|
16863
|
+
p: p2,
|
|
15620
16864
|
m,
|
|
15621
16865
|
bg = "transparent",
|
|
15622
16866
|
border = false,
|
|
@@ -15629,7 +16873,7 @@ function BoxPattern({
|
|
|
15629
16873
|
return /* @__PURE__ */ jsx(
|
|
15630
16874
|
Box,
|
|
15631
16875
|
{
|
|
15632
|
-
padding:
|
|
16876
|
+
padding: p2,
|
|
15633
16877
|
margin: m,
|
|
15634
16878
|
bg,
|
|
15635
16879
|
border,
|
|
@@ -16542,79 +17786,87 @@ var COMPONENT_REGISTRY = {
|
|
|
16542
17786
|
// Map patterns
|
|
16543
17787
|
MapViewPattern,
|
|
16544
17788
|
// Custom pattern
|
|
16545
|
-
CustomPattern
|
|
16546
|
-
|
|
16547
|
-
|
|
16548
|
-
|
|
16549
|
-
|
|
16550
|
-
|
|
16551
|
-
|
|
16552
|
-
|
|
16553
|
-
|
|
16554
|
-
|
|
16555
|
-
|
|
16556
|
-
|
|
16557
|
-
|
|
16558
|
-
|
|
16559
|
-
|
|
16560
|
-
|
|
16561
|
-
|
|
16562
|
-
|
|
16563
|
-
|
|
16564
|
-
|
|
16565
|
-
|
|
16566
|
-
|
|
16567
|
-
|
|
16568
|
-
|
|
16569
|
-
|
|
16570
|
-
|
|
16571
|
-
|
|
16572
|
-
|
|
16573
|
-
|
|
16574
|
-
|
|
16575
|
-
|
|
16576
|
-
|
|
16577
|
-
|
|
16578
|
-
|
|
16579
|
-
|
|
16580
|
-
|
|
16581
|
-
|
|
16582
|
-
|
|
16583
|
-
|
|
16584
|
-
|
|
16585
|
-
|
|
16586
|
-
|
|
16587
|
-
|
|
16588
|
-
|
|
16589
|
-
|
|
16590
|
-
|
|
16591
|
-
|
|
16592
|
-
|
|
16593
|
-
|
|
16594
|
-
|
|
16595
|
-
|
|
16596
|
-
|
|
16597
|
-
|
|
16598
|
-
|
|
16599
|
-
|
|
16600
|
-
|
|
16601
|
-
|
|
16602
|
-
|
|
16603
|
-
|
|
16604
|
-
|
|
16605
|
-
|
|
16606
|
-
|
|
16607
|
-
|
|
16608
|
-
|
|
17789
|
+
CustomPattern,
|
|
17790
|
+
// Direct components (not wrapped in pattern adapters)
|
|
17791
|
+
Stack,
|
|
17792
|
+
VStack: VStackPattern,
|
|
17793
|
+
HStack: HStackPattern,
|
|
17794
|
+
Typography: TextPattern,
|
|
17795
|
+
Tabs,
|
|
17796
|
+
StatDisplay,
|
|
17797
|
+
StatBadge,
|
|
17798
|
+
StatusDot,
|
|
17799
|
+
TrendIndicator,
|
|
17800
|
+
RangeSlider,
|
|
17801
|
+
StarRating,
|
|
17802
|
+
LineChart,
|
|
17803
|
+
DataGrid,
|
|
17804
|
+
DataList,
|
|
17805
|
+
CalendarGrid,
|
|
17806
|
+
Lightbox,
|
|
17807
|
+
UploadDropZone,
|
|
17808
|
+
WizardNavigation,
|
|
17809
|
+
WizardProgress,
|
|
17810
|
+
Meter,
|
|
17811
|
+
ActionButtons,
|
|
17812
|
+
HealthBar,
|
|
17813
|
+
ScoreDisplay,
|
|
17814
|
+
DPad,
|
|
17815
|
+
CanvasEffect,
|
|
17816
|
+
CombatLog,
|
|
17817
|
+
DialogueBox,
|
|
17818
|
+
InventoryPanel,
|
|
17819
|
+
GameHud,
|
|
17820
|
+
GameMenu,
|
|
17821
|
+
FilterGroup: ButtonGroup,
|
|
17822
|
+
ErrorState: EmptyState,
|
|
17823
|
+
Toast: AlertPattern,
|
|
17824
|
+
// Plain component name aliases — component-mapping.json returns these names
|
|
17825
|
+
// (e.g., "button" → "Button") but registry above uses "ButtonPattern" keys.
|
|
17826
|
+
Button: ButtonPattern,
|
|
17827
|
+
IconButton: IconButtonPattern,
|
|
17828
|
+
Link: LinkPattern,
|
|
17829
|
+
Text: TextPattern,
|
|
17830
|
+
Heading: HeadingPattern,
|
|
17831
|
+
Badge: BadgePattern,
|
|
17832
|
+
Avatar: AvatarPattern,
|
|
17833
|
+
Icon: IconPattern,
|
|
17834
|
+
Image: ImagePattern,
|
|
17835
|
+
Card: CardPattern,
|
|
17836
|
+
ProgressBar: ProgressBarPattern,
|
|
17837
|
+
Spinner: SpinnerPattern,
|
|
17838
|
+
Input: InputPattern,
|
|
17839
|
+
Textarea: TextareaPattern,
|
|
17840
|
+
Select: SelectPattern,
|
|
17841
|
+
Checkbox: CheckboxPattern,
|
|
17842
|
+
Radio: RadioPattern,
|
|
17843
|
+
Label: LabelPattern,
|
|
17844
|
+
Alert: AlertPattern,
|
|
17845
|
+
Tooltip: TooltipPattern,
|
|
17846
|
+
Popover: PopoverPattern,
|
|
17847
|
+
Menu: MenuPattern,
|
|
17848
|
+
Accordion: AccordionPattern,
|
|
17849
|
+
Container: ContainerPattern,
|
|
17850
|
+
SimpleGrid: SimpleGridPattern,
|
|
17851
|
+
FloatButton: FloatButtonPattern,
|
|
17852
|
+
MapView: MapViewPattern,
|
|
17853
|
+
Box: BoxPattern,
|
|
17854
|
+
Grid: GridPattern,
|
|
17855
|
+
Center: CenterPattern,
|
|
17856
|
+
Spacer: SpacerPattern,
|
|
17857
|
+
Divider: DividerPattern
|
|
16609
17858
|
};
|
|
16610
17859
|
function getComponentForPattern(patternType) {
|
|
16611
|
-
const
|
|
16612
|
-
if (!
|
|
17860
|
+
const mapping = getComponentForPattern$1(patternType);
|
|
17861
|
+
if (!mapping) {
|
|
16613
17862
|
return null;
|
|
16614
17863
|
}
|
|
16615
|
-
|
|
17864
|
+
const name = typeof mapping === "string" ? mapping : mapping.component;
|
|
17865
|
+
if (!name) return null;
|
|
17866
|
+
return COMPONENT_REGISTRY[name] ?? null;
|
|
16616
17867
|
}
|
|
16617
17868
|
var PATTERNS_WITH_CHILDREN = /* @__PURE__ */ new Set([
|
|
17869
|
+
"stack",
|
|
16618
17870
|
"vstack",
|
|
16619
17871
|
"hstack",
|
|
16620
17872
|
"box",
|
|
@@ -16886,7 +18138,7 @@ function SlotContentRenderer({
|
|
|
16886
18138
|
className: "slot-content",
|
|
16887
18139
|
"data-pattern": content.pattern,
|
|
16888
18140
|
"data-id": content.id,
|
|
16889
|
-
children: /* @__PURE__ */ jsx(PatternComponent, { ...restProps,
|
|
18141
|
+
children: /* @__PURE__ */ jsx(PatternComponent, { ...restProps, children: renderedChildren })
|
|
16890
18142
|
}
|
|
16891
18143
|
);
|
|
16892
18144
|
}
|
|
@@ -16948,4 +18200,4 @@ function UISlotRenderer({
|
|
|
16948
18200
|
}
|
|
16949
18201
|
UISlotRenderer.displayName = "UISlotRenderer";
|
|
16950
18202
|
|
|
16951
|
-
export { Accordion, ActionButton, ActionButtons, Alert, AnimatedCounter, Avatar, Badge, Box, Breadcrumb, Button, ButtonGroup, CalendarGrid, Card, Card2, 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, IsometricCanvas_default, 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, SuspenseConfigProvider, 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 };
|
|
18203
|
+
export { Accordion, ActionButton, ActionButtons, Alert, AnimatedCounter, Avatar, Badge, Box, Breadcrumb, Button, ButtonGroup, CalendarGrid, CanvasEffect, Card, Card2, 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, IsometricCanvas_default, 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, SuspenseConfigProvider, 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 };
|