Release Healer Man 0.1.30
This commit is contained in:
1 parent
544c1bee91
commit
7d7fd567f0
845 files changed
+2040
-87
No files matched your search
@@ -7,6 +7,7 @@ import {
|
||||
DoubleSide,
|
||||
DynamicDrawUsage,
|
||||
Group,
|
||||
Matrix4,
|
||||
MeshBasicMaterial,
|
||||
MultiplyBlending,
|
||||
NormalBlending,
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
preloadCombatEffectManifest,
|
||||
type CombatEffectPhaseAsset,
|
||||
type CombatPresentationEvent,
|
||||
type CombatPresentationPhase,
|
||||
type CombatVfxQuality,
|
||||
} from "../game/combatPresentation";
|
||||
import { fetchCombatMediaAsset } from "../game/combatMediaPack";
|
||||
@@ -48,17 +50,30 @@ import {
|
||||
type OriginalCombatMeshGraph,
|
||||
} from "./originalCombatMeshRuntime";
|
||||
import { updateCombatEffectAnchorPosition } from "./combatEffectAnchorRegistry";
|
||||
import { OriginalWowModel } from "./OriginalWowModel";
|
||||
import { OriginalParticleGeometry } from "./OriginalParticleGeometry";
|
||||
import {
|
||||
parseOriginalWowModelGraph, updateWowBoneMatrices, wowModelFrame, wowModelDurationMs, wowModelTextureUrls, wowParticleChild,
|
||||
type OriginalWowModelGraph,
|
||||
} from "./originalWowModelRuntime";
|
||||
|
||||
type OriginalSourceEffectGraph = OriginalCombatEffectGraph | OriginalCombatMeshGraph;
|
||||
type OriginalSourceEffectGraph = OriginalCombatEffectGraph | OriginalCombatMeshGraph | OriginalWowModelGraph;
|
||||
|
||||
function sourceParticles(graph: OriginalSourceEffectGraph): OriginalCombatEffectGraph | null {
|
||||
return graph.kind === "wow-m2-model" ? graph.particleGraph
|
||||
: graph.kind === "wow-m2-particle-effect" ? graph : null;
|
||||
}
|
||||
|
||||
interface LoadedOriginalGraph {
|
||||
readonly asset: CombatEffectPhaseAsset;
|
||||
readonly graph: OriginalSourceEffectGraph;
|
||||
readonly textures: ReadonlyMap<number, Texture>;
|
||||
readonly sourceTextures: ReadonlyMap<string, Texture>;
|
||||
}
|
||||
|
||||
interface OriginalCombatEffectProps {
|
||||
readonly event: CombatPresentationEvent;
|
||||
readonly alternateOriginalPhase?: CombatPresentationPhase;
|
||||
readonly quality: Exclude<CombatVfxQuality, "off">;
|
||||
readonly active: boolean;
|
||||
readonly onDone: () => void;
|
||||
@@ -210,7 +225,8 @@ async function loadGraph(url: string): Promise<OriginalSourceEffectGraph> {
|
||||
? response.json()
|
||||
: Promise.reject(new Error(`Original effect graph request failed (${response.status}): ${url}`)))
|
||||
.then((value) => (
|
||||
value && typeof value === "object" && (value as { kind?: unknown }).kind === "wow-m2-static-mesh"
|
||||
value?.kind === "wow-m2-model" ? parseOriginalWowModelGraph(value)
|
||||
: value && typeof value === "object" && (value as { kind?: unknown }).kind === "wow-m2-static-mesh"
|
||||
? parseOriginalCombatMeshGraph(value)
|
||||
: parseOriginalCombatEffectGraph(value)
|
||||
));
|
||||
@@ -243,7 +259,13 @@ async function loadTexture(url: string): Promise<Texture> {
|
||||
async function loadOriginalGraphAsset(asset: CombatEffectPhaseAsset): Promise<LoadedOriginalGraph> {
|
||||
if (!asset.effectGraphUrl) throw new Error("Original effect phase has no graph URL.");
|
||||
const graph = await loadGraph(asset.effectGraphUrl);
|
||||
const textureIndices = graph.kind === "wow-m2-static-mesh"
|
||||
const sourceTextures = new Map<string, Texture>();
|
||||
if (graph.kind === "wow-m2-model") {
|
||||
await Promise.all(wowModelTextureUrls(graph).map(async (url) => sourceTextures.set(url, await loadTexture(url))));
|
||||
}
|
||||
const textureIndices = graph.kind === "wow-m2-model"
|
||||
? graph.textures.flatMap((texture, index) => texture.url ? [index] : [])
|
||||
: graph.kind === "wow-m2-static-mesh"
|
||||
? [...new Set(graph.layers.map((layer) => layer.textureIndex))]
|
||||
: [...new Set(graph.runtime.readyParticleIndices.map((particleIndex) => (
|
||||
graph.particles[particleIndex]?.textureIndex
|
||||
@@ -253,7 +275,7 @@ async function loadOriginalGraphAsset(asset: CombatEffectPhaseAsset): Promise<Lo
|
||||
if (!url) throw new Error(`Original effect texture ${textureIndex} has no packaged URL.`);
|
||||
return [textureIndex, await loadTexture(url)] as const;
|
||||
}));
|
||||
return { asset, graph, textures: new Map(loadedTextures) };
|
||||
return { asset, graph, textures: new Map(loadedTextures), sourceTextures };
|
||||
}
|
||||
|
||||
function materialForParticle(particle: OriginalCombatEffectParticle, texture: Texture): ShaderMaterial {
|
||||
@@ -278,7 +300,7 @@ function materialForParticle(particle: OriginalCombatEffectParticle, texture: Te
|
||||
});
|
||||
}
|
||||
|
||||
const OriginalParticleEmitter = memo(function OriginalParticleEmitter({
|
||||
export const OriginalParticleEmitter = memo(function OriginalParticleEmitter({
|
||||
graph,
|
||||
particle,
|
||||
texture,
|
||||
@@ -287,6 +309,9 @@ const OriginalParticleEmitter = memo(function OriginalParticleEmitter({
|
||||
eventId,
|
||||
emitterIndex,
|
||||
loopTimeline,
|
||||
modelGraph,
|
||||
sourceTextures,
|
||||
timelineRef,
|
||||
}: {
|
||||
readonly graph: OriginalCombatEffectGraph;
|
||||
readonly particle: OriginalCombatEffectParticle;
|
||||
@@ -296,8 +321,17 @@ const OriginalParticleEmitter = memo(function OriginalParticleEmitter({
|
||||
readonly eventId: number;
|
||||
readonly emitterIndex: number;
|
||||
readonly loopTimeline: boolean;
|
||||
readonly modelGraph?: OriginalWowModelGraph;
|
||||
readonly sourceTextures?: ReadonlyMap<string, Texture>;
|
||||
readonly timelineRef?: MutableRefObject<number>;
|
||||
}) {
|
||||
const elapsedRef = useRef(0);
|
||||
const sourcePose = useMemo(() => ({
|
||||
bones: modelGraph?.bones.map(() => new Matrix4()) ?? [],
|
||||
births: new Float64Array(budget).fill(Number.NaN),
|
||||
birthMatrices: Array.from({ length: modelGraph ? budget : 0 }, () => new Matrix4()),
|
||||
position: new Vector3(), velocity: new Vector3(), origin: new Vector3(),
|
||||
}), [modelGraph, budget]);
|
||||
const geometry = useMemo(() => {
|
||||
const next = new BufferGeometry();
|
||||
const position = new BufferAttribute(new Float32Array(budget * 3), 3);
|
||||
@@ -315,6 +349,7 @@ const OriginalParticleEmitter = memo(function OriginalParticleEmitter({
|
||||
next.setAttribute("particleAlpha", alpha);
|
||||
next.setAttribute("particleSize", size);
|
||||
next.setAttribute("particleFrame", frame);
|
||||
next.setAttribute("particleAge", new BufferAttribute(new Float32Array(budget), 1));
|
||||
next.setDrawRange(0, budget);
|
||||
return next;
|
||||
}, [budget]);
|
||||
@@ -330,7 +365,8 @@ const OriginalParticleEmitter = memo(function OriginalParticleEmitter({
|
||||
|
||||
useFrame((state, delta) => {
|
||||
if (!active || budget === 0) return;
|
||||
elapsedRef.current += Math.min(0.1, Math.max(0, delta));
|
||||
if (timelineRef) elapsedRef.current = timelineRef.current;
|
||||
else elapsedRef.current += Math.min(0.1, Math.max(0, delta));
|
||||
material.uniforms.viewportHeight.value = state.size.height * state.viewport.dpr;
|
||||
const elapsed = elapsedRef.current;
|
||||
const positions = geometry.getAttribute("position") as BufferAttribute;
|
||||
@@ -338,6 +374,7 @@ const OriginalParticleEmitter = memo(function OriginalParticleEmitter({
|
||||
const alphas = geometry.getAttribute("particleAlpha") as BufferAttribute;
|
||||
const sizes = geometry.getAttribute("particleSize") as BufferAttribute;
|
||||
const frames = geometry.getAttribute("particleFrame") as BufferAttribute;
|
||||
const ages = geometry.getAttribute("particleAge") as BufferAttribute;
|
||||
const cycleLength = Math.max(0.02, peakLifespan);
|
||||
const emitterSeed = eventId * 1_009
|
||||
+ particle.id * 17
|
||||
@@ -349,6 +386,7 @@ const OriginalParticleEmitter = memo(function OriginalParticleEmitter({
|
||||
const completedCycles = Math.floor((elapsed - slotOffset) / cycleLength);
|
||||
const birthTime = slotOffset + Math.max(0, completedCycles) * cycleLength;
|
||||
const age = elapsed - birthTime;
|
||||
ages.setX(index, Math.max(0, age));
|
||||
const cycleSeed = emitterSeed + index * 97 + Math.max(0, completedCycles) * 271;
|
||||
const rawTimelineMs = birthTime * 1_000;
|
||||
const timelineMs = loopTimeline ? rawTimelineMs % durationMs : rawTimelineMs;
|
||||
@@ -370,7 +408,7 @@ const OriginalParticleEmitter = memo(function OriginalParticleEmitter({
|
||||
const accepted = elapsed >= slotOffset
|
||||
&& age >= 0
|
||||
&& age < lifespan
|
||||
&& scalarAt("enabled") > 0.5
|
||||
&& (particle.samples.enabled.length === 0 || scalarAt("enabled") > 0.5)
|
||||
&& sourceRandom(cycleSeed + 2) <= Math.min(1, emissionRate / Math.max(peakRate, 0.001));
|
||||
if (!accepted) {
|
||||
alphas.setX(index, 0);
|
||||
@@ -408,12 +446,22 @@ const OriginalParticleEmitter = memo(function OriginalParticleEmitter({
|
||||
localZ += sphereZ * Math.max(areaWidth, areaLength);
|
||||
}
|
||||
// WoW M2 is Z-up; the game scene is Y-up.
|
||||
positions.setXYZ(
|
||||
index,
|
||||
particle.position[0] + localX + velocityX * age,
|
||||
particle.position[2] + localZ + velocityZ * age - gravity * age * age * 0.5,
|
||||
-particle.position[1] - localY - velocityY * age,
|
||||
);
|
||||
sourcePose.position.set(particle.position[0] + localX, particle.position[1] + localY, particle.position[2] + localZ);
|
||||
sourcePose.velocity.set(velocityX * age, velocityY * age, velocityZ * age);
|
||||
if (modelGraph && sourcePose.bones[particle.parentBone]) {
|
||||
if (sourcePose.births[index] !== rawTimelineMs) {
|
||||
updateWowBoneMatrices(modelGraph, wowModelFrame(modelGraph, rawTimelineMs, loopTimeline), sourcePose.bones);
|
||||
sourcePose.birthMatrices[index].copy(sourcePose.bones[particle.parentBone]);
|
||||
sourcePose.births[index] = rawTimelineMs;
|
||||
}
|
||||
const bone = sourcePose.birthMatrices[index];
|
||||
sourcePose.position.applyMatrix4(bone);
|
||||
sourcePose.origin.set(0, 0, 0).applyMatrix4(bone);
|
||||
sourcePose.velocity.applyMatrix4(bone).sub(sourcePose.origin);
|
||||
}
|
||||
sourcePose.position.add(sourcePose.velocity);
|
||||
positions.setXYZ(index, sourcePose.position.x,
|
||||
sourcePose.position.z - gravity * age * age * 0.5, -sourcePose.position.y);
|
||||
const color = interpolateOriginalLifetime(particle.samples.color, lifeProgress) ?? [255, 255, 255];
|
||||
const alpha = interpolateOriginalLifetime(particle.samples.alpha, lifeProgress) ?? 0;
|
||||
const scale = interpolateOriginalLifetime(particle.samples.scale, lifeProgress) ?? [0, 0];
|
||||
@@ -435,11 +483,52 @@ const OriginalParticleEmitter = memo(function OriginalParticleEmitter({
|
||||
frames.needsUpdate = true;
|
||||
});
|
||||
|
||||
const geometryChild = wowParticleChild(modelGraph, particle.geometryModel);
|
||||
const recursiveChild = wowParticleChild(modelGraph, particle.recursionModel);
|
||||
return budget > 0 ? (
|
||||
<points geometry={geometry} material={material} frustumCulled={false} />
|
||||
<group>
|
||||
{geometryChild && sourceTextures ? <OriginalParticleGeometry graph={geometryChild} source={geometry}
|
||||
textures={sourceTextures} budget={budget} active={active} /> : !recursiveChild
|
||||
? <points geometry={geometry} material={material} frustumCulled={false} /> : null}
|
||||
{recursiveChild && sourceTextures && <OriginalRecursiveParticles graph={recursiveChild} source={geometry}
|
||||
textures={sourceTextures} budget={budget} active={active} eventId={eventId} />}
|
||||
</group>
|
||||
) : null;
|
||||
});
|
||||
|
||||
const OriginalRecursiveParticles = memo(function OriginalRecursiveParticles({ graph, source, textures, budget, active, eventId }: {
|
||||
readonly graph: OriginalWowModelGraph; readonly source: BufferGeometry; readonly textures: ReadonlyMap<string, Texture>;
|
||||
readonly budget: number; readonly active: boolean; readonly eventId: number;
|
||||
}) {
|
||||
// Recursive effects share the parent's particle pool. Bound the secondary
|
||||
// emitters so large dispels cannot multiply the entire effect budget.
|
||||
const slots = useMemo(() => Array.from({ length: Math.min(8, budget) }, () => ({
|
||||
group: new Group(), age: { current: 0 },
|
||||
})), [budget]);
|
||||
useFrame(() => {
|
||||
if (!active) return;
|
||||
const positions = source.getAttribute("position"), alpha = source.getAttribute("particleAlpha");
|
||||
const sizes = source.getAttribute("particleSize"), ages = source.getAttribute("particleAge");
|
||||
slots.forEach((slot, index) => {
|
||||
slot.group.position.fromBufferAttribute(positions, index);
|
||||
slot.group.visible = alpha.getX(index) > 0;
|
||||
slot.group.scale.setScalar(sizes.getX(index)); slot.age.current = ages.getX(index);
|
||||
});
|
||||
});
|
||||
const particles = graph.particleGraph;
|
||||
return <group name="original-wow-recursive-particles">{slots.map((slot, slotIndex) =>
|
||||
<primitive key={slotIndex} object={slot.group}>
|
||||
{particles?.runtime.readyParticleIndices.map((index) => {
|
||||
const particle = particles.particles[index];
|
||||
const texture = textures.get(graph.textures[particle.textureIndex].url!);
|
||||
return texture && <OriginalParticleEmitter key={index} graph={particles} modelGraph={graph} particle={particle}
|
||||
texture={texture} budget={Math.min(16, originalEmitterParticleBudget(particle, "high"))} active={active}
|
||||
eventId={eventId} emitterIndex={slotIndex * 32 + index} loopTimeline timelineRef={slot.age} sourceTextures={textures} />;
|
||||
})}
|
||||
</primitive>,
|
||||
)}</group>;
|
||||
});
|
||||
|
||||
function meshBlendMode(blendMode: number) {
|
||||
if (blendMode === 3 || blendMode === 4 || blendMode === 7) return AdditiveBlending;
|
||||
if (blendMode === 5 || blendMode === 6) return MultiplyBlending;
|
||||
@@ -569,8 +658,8 @@ function LoadedOriginalCombatEffect({
|
||||
const staticMeshPlaybackRef = useRef({ elapsedMs: 0, decaying: false });
|
||||
const particleBudgets = useMemo(() => {
|
||||
const emitters = loaded.flatMap((item, loadedIndex) => {
|
||||
const graph = item.graph;
|
||||
return graph.kind === "wow-m2-static-mesh"
|
||||
const graph = sourceParticles(item.graph);
|
||||
return !graph
|
||||
? []
|
||||
: graph.runtime.readyParticleIndices.map((particleIndex) => ({
|
||||
key: `${loadedIndex}:${particleIndex}`,
|
||||
@@ -595,7 +684,9 @@ function LoadedOriginalCombatEffect({
|
||||
const longestGraph = loaded.reduce((maximum, item) => (
|
||||
Math.max(
|
||||
maximum,
|
||||
item.graph.kind === "wow-m2-static-mesh"
|
||||
item.graph.kind === "wow-m2-model" ? wowModelDurationMs(item.graph)
|
||||
+ (item.graph.particleGraph ? graphTailMs(item.graph.particleGraph) : 0)
|
||||
: item.graph.kind === "wow-m2-static-mesh"
|
||||
? 6_000
|
||||
: graphDurationMs(item.graph) + graphTailMs(item.graph),
|
||||
)
|
||||
@@ -604,7 +695,7 @@ function LoadedOriginalCombatEffect({
|
||||
return Math.max(120, Math.min(6_000, longestGraph));
|
||||
}, [event.durationMs, event.phase, loaded]);
|
||||
const staticMeshDecayDurationMs = useMemo(() => loaded.reduce((maximum, item) => (
|
||||
item.graph.kind === "wow-m2-static-mesh"
|
||||
item.graph.kind === "wow-m2-static-mesh" || item.graph.kind === "wow-m2-model"
|
||||
? Math.max(
|
||||
maximum,
|
||||
item.graph.animations.find((animation) => animation.id === 159)?.durationMs ?? 0,
|
||||
@@ -695,6 +786,8 @@ function LoadedOriginalCombatEffect({
|
||||
position={[0, effectPlacementHeight(item.asset, graph, event), 0]}
|
||||
scale={scale}
|
||||
>
|
||||
{graph.kind === "wow-m2-model" && <OriginalWowModel graph={graph} textures={item.textures}
|
||||
active={active} playbackRef={staticMeshPlaybackRef} loop={loopTimeline} />}
|
||||
{graph.kind === "wow-m2-static-mesh" ? (
|
||||
<OriginalStaticMesh
|
||||
graph={graph}
|
||||
@@ -702,19 +795,22 @@ function LoadedOriginalCombatEffect({
|
||||
active={active}
|
||||
playbackRef={staticMeshPlaybackRef}
|
||||
/>
|
||||
) : graph.runtime.readyParticleIndices.map((particleIndex) => {
|
||||
const particle = graph.particles[particleIndex];
|
||||
) : sourceParticles(graph)?.runtime.readyParticleIndices.map((particleIndex) => {
|
||||
const particles = sourceParticles(graph)!;
|
||||
const particle = particles.particles[particleIndex];
|
||||
const texture = item.textures.get(particle?.textureIndex ?? -1);
|
||||
return particle && texture ? (
|
||||
<OriginalParticleEmitter
|
||||
key={`${particleIndex}:${particle.id}:${particle.textureIndex}`}
|
||||
graph={graph}
|
||||
particle={particle}
|
||||
texture={texture}
|
||||
budget={particleBudgets.get(`${loadedIndex}:${particleIndex}`) ?? 0}
|
||||
active={active}
|
||||
eventId={event.id}
|
||||
emitterIndex={loadedIndex * 4_096 + particleIndex}
|
||||
graph={particles}
|
||||
modelGraph={graph.kind === "wow-m2-model" ? graph : undefined}
|
||||
sourceTextures={item.sourceTextures}
|
||||
particle={particle}
|
||||
texture={texture}
|
||||
budget={particleBudgets.get(`${loadedIndex}:${particleIndex}`) ?? 0}
|
||||
active={active}
|
||||
eventId={event.id}
|
||||
emitterIndex={loadedIndex * 4_096 + particleIndex}
|
||||
loopTimeline={loopTimeline}
|
||||
/>
|
||||
) : null;
|
||||
@@ -728,47 +824,91 @@ function LoadedOriginalCombatEffect({
|
||||
|
||||
export const OriginalCombatEffect = memo(function OriginalCombatEffect({
|
||||
event,
|
||||
alternateOriginalPhase,
|
||||
quality,
|
||||
active,
|
||||
onDone,
|
||||
fallback,
|
||||
}: OriginalCombatEffectProps & { readonly fallback: ReactNode }) {
|
||||
const [loaded, setLoaded] = useState<readonly LoadedOriginalGraph[] | null>(null);
|
||||
const [renderEvent, setRenderEvent] = useState(event);
|
||||
const completedMissingOriginalRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
setLoaded(null);
|
||||
setRenderEvent(event);
|
||||
completedMissingOriginalRef.current = false;
|
||||
void preloadCombatEffectManifest().then(async () => {
|
||||
const assets = combatOriginalPhaseAssets(event);
|
||||
const settled = await Promise.allSettled(assets.map(loadOriginalGraphAsset));
|
||||
const loadEvent = async (candidate: CombatPresentationEvent) => {
|
||||
const assets = combatOriginalPhaseAssets(candidate);
|
||||
const settled = await Promise.allSettled(assets.map(loadOriginalGraphAsset));
|
||||
return {
|
||||
event: candidate,
|
||||
assets,
|
||||
settled,
|
||||
ready: settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []),
|
||||
};
|
||||
};
|
||||
const primary = await loadEvent(event);
|
||||
const attempts = [primary];
|
||||
let selected = primary;
|
||||
if (!cancelled && selected.ready.length === 0 && alternateOriginalPhase) {
|
||||
const alternate = await loadEvent({
|
||||
...event,
|
||||
id: -event.id,
|
||||
phase: alternateOriginalPhase,
|
||||
});
|
||||
attempts.push(alternate);
|
||||
if (alternate.ready.length > 0) selected = alternate;
|
||||
}
|
||||
if (cancelled) return;
|
||||
const ready = settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
||||
if (import.meta.env.DEV && assets.length > ready.length) {
|
||||
const failures = settled.flatMap((result) => (
|
||||
result.status === "rejected"
|
||||
? [result.reason instanceof Error ? result.reason.message : String(result.reason)]
|
||||
: []
|
||||
));
|
||||
console.warn(
|
||||
`Original spell effect failed to load for ${event.name} (${event.phase}).`,
|
||||
failures,
|
||||
);
|
||||
if (import.meta.env.DEV) {
|
||||
for (const attempt of attempts) {
|
||||
if (attempt.assets.length <= attempt.ready.length) continue;
|
||||
const failures = attempt.settled.flatMap((result) => (
|
||||
result.status === "rejected"
|
||||
? [result.reason instanceof Error ? result.reason.message : String(result.reason)]
|
||||
: []
|
||||
));
|
||||
console.warn(
|
||||
`Original spell effect failed to load for ${attempt.event.name} (${attempt.event.phase}).`,
|
||||
failures,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Keep any usable original layers; only substitute when none can render.
|
||||
setLoaded(ready);
|
||||
setRenderEvent(selected.event);
|
||||
setLoaded(selected.ready);
|
||||
}).catch(() => {
|
||||
if (!cancelled) setLoaded([]);
|
||||
if (!cancelled) {
|
||||
setRenderEvent(event);
|
||||
setLoaded([]);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [event]);
|
||||
}, [alternateOriginalPhase, event]);
|
||||
|
||||
if (loaded === null) return null;
|
||||
const selection = selectCombatEffectRender("original", loaded.map((item) => item.asset));
|
||||
const selection = loaded === null
|
||||
? null
|
||||
: selectCombatEffectRender("original", loaded.map((item) => item.asset));
|
||||
useEffect(() => {
|
||||
if (
|
||||
loaded === null
|
||||
|| selection?.kind === "original"
|
||||
|| fallback !== null
|
||||
|| completedMissingOriginalRef.current
|
||||
) return;
|
||||
completedMissingOriginalRef.current = true;
|
||||
onDone();
|
||||
}, [fallback, loaded, onDone, selection?.kind]);
|
||||
|
||||
if (loaded === null || selection === null) return null;
|
||||
return selection.kind === "original" ? (
|
||||
<LoadedOriginalCombatEffect
|
||||
event={event}
|
||||
event={renderEvent}
|
||||
quality={quality}
|
||||
active={active}
|
||||
loaded={loaded}
|
||||
@@ -777,6 +917,7 @@ export const OriginalCombatEffect = memo(function OriginalCombatEffect({
|
||||
) : fallback;
|
||||
}, (previous, next) => (
|
||||
previous.event === next.event
|
||||
&& previous.alternateOriginalPhase === next.alternateOriginalPhase
|
||||
&& previous.quality === next.quality
|
||||
&& previous.active === next.active
|
||||
));
|
||||
Reference in new issue
Block a user