import { SRGBColorSpace, Texture, TextureLoader } from "three"; import { fetchCombatMediaAsset, combatMediaPackReady } from "../game/combatMediaPack"; import { preloadCombatEffectManifest, shadowfiendOriginalModelAsset } from "../game/combatPresentation"; import { parseOriginalWowModelGraph, type OriginalWowModelGraph } from "./originalWowModelRuntime"; export interface ShadowfiendMedia { readonly graph: OriginalWowModelGraph; readonly textures: ReadonlyMap; } let shadowfiendMediaRequest: Promise | null = null; let preparedShadowfiendMedia: ShadowfiendMedia | null = null; export function getPreparedShadowfiendMedia(): ShadowfiendMedia | null { return combatMediaPackReady() ? preparedShadowfiendMedia : null; } /** Prepare the immutable client model once, before a Priest can cast in gameplay. */ export function preloadShadowfiendMedia(): Promise { if (!combatMediaPackReady()) return Promise.resolve(null); if (shadowfiendMediaRequest) return shadowfiendMediaRequest; shadowfiendMediaRequest = (async () => { await preloadCombatEffectManifest(); const url = shadowfiendOriginalModelAsset()?.effectGraphUrl; if (!url) return null; const response = await fetchCombatMediaAsset(url); if (!response.ok) throw new Error(`Shadowfiend model request failed: ${response.status}`); const graph = parseOriginalWowModelGraph(await response.json()); const textures = new Map(); const ownedTextures: Texture[] = []; const loader = new TextureLoader(); try { for (const [index, source] of graph.textures.entries()) { if (!source.url) continue; const image = await fetchCombatMediaAsset(source.url); if (!image.ok) throw new Error(`Shadowfiend texture request failed: ${image.status}`); const objectUrl = URL.createObjectURL(await image.blob()); try { const texture = await loader.loadAsync(objectUrl); texture.colorSpace = SRGBColorSpace; ownedTextures.push(texture); textures.set(index, texture); } finally { URL.revokeObjectURL(objectUrl); } } preparedShadowfiendMedia = { graph, textures }; return preparedShadowfiendMedia; } catch (error) { ownedTextures.forEach((texture) => texture.dispose()); throw error; } })().catch((error: unknown) => { shadowfiendMediaRequest = null; throw error; }); return shadowfiendMediaRequest; }