import { useEffect, useRef, useState, useSyncExternalStore } from "react"; import { useFrame } from "@react-three/fiber"; import { DoubleSide, Group } from "three"; import { subscribeContentManager } from "../content/contentManager"; import { combatMediaPackReady } from "../game/combatMediaPack"; import { type CombatSummon, useCombatStore } from "../game/combatStore"; import { getMobPosition } from "../game/mobRuntimeRegistry"; import { OriginalWowModel } from "./OriginalWowModel"; import { getPreparedShadowfiendMedia, preloadShadowfiendMedia } from "./shadowfiendMedia"; const SHADOWFIEND_CREATURE_ID = 19668; function ShadowfiendFallback() { return {[-1, 1].map((side) => )} {[-1, 1].map((side) => )} ; } function ShadowfiendMember({ summon, active }: { readonly summon: CombatSummon; readonly active: boolean }) { const [media, setMedia] = useState(getPreparedShadowfiendMedia); const mediaReady = useSyncExternalStore(subscribeContentManager, combatMediaPackReady, () => false); const group = useRef(null); const playback = useRef({ elapsedMs: 0, decaying: false }); useEffect(() => { if (!mediaReady) { setMedia(null); return; } let cancelled = false; void preloadShadowfiendMedia().then((ready) => { if (!cancelled) setMedia(ready); }).catch(() => { if (!cancelled) setMedia(null); }); return () => { cancelled = true; }; }, [mediaReady]); useFrame((_, delta) => { if (!group.current) return; const live = useCombatStore.getState().summons.find((candidate) => candidate.id === summon.id); if (!live) return; const goal = live.position; const blend = Math.min(1, delta * 9); group.current.position.x += (goal[0] - group.current.position.x) * blend; group.current.position.y += (goal[1] + 0.13 - group.current.position.y) * blend; group.current.position.z += (goal[2] - group.current.position.z) * blend; const target = live.targetId ? getMobPosition(live.targetId) : null; if (target) group.current.rotation.y = Math.atan2(goal[2] - target[2], target[0] - goal[0]); if (active) playback.current.elapsedMs += Math.min(delta, 0.1) * 1_000; }); return {media ? : } ; } /** Player and companion combat summons live outside the static dungeon population. */ export function CombatSummonPopulation({ active }: { readonly active: boolean }) { const summons = useCombatStore((state) => state.summons); return {summons.filter((summon) => summon.creatureId === SHADOWFIEND_CREATURE_ID && summon.health > 0) .map((summon) => )} ; }