Files
healer-man/src/scene/shadowfiendMedia.ts
T
2026-09-27 18:46:30 -04:00

59 lines
2.5 KiB
TypeScript

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<number, Texture>;
}
let shadowfiendMediaRequest: Promise<ShadowfiendMedia | null> | 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<ShadowfiendMedia | null> {
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<number, Texture>();
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;
}