import { contentPackIsUsable, getContentManagerSnapshot, installContentPack, removeContentPack, resolveContentUrl, subscribeContentManager, } from "../content/contentManager"; export const COMBAT_MEDIA_MANIFEST_URL = "/assets/game/combat-effects/manifest.json"; export const COMBAT_MEDIA_CONTENT_PACK_ID = "core:combat-media"; export interface CombatMediaDownloadProgress { readonly completed: number; readonly total: number; } export interface CombatMediaDownloadResult { readonly fileCount: number; } type ProgressListener = (progress: CombatMediaDownloadProgress) => void; function isCombatMediaUrl(value: unknown): value is string { return typeof value === "string" && value.startsWith("/assets/game/combat-effects/"); } /** Callers must enforce the player's original-media opt-in before fetching. */ export async function fetchCombatMediaAsset(url: string): Promise { if (!isCombatMediaUrl(url)) throw new Error(`Unsupported combat media URL: ${url}`); return fetchOptionalMediaAsset(url); } /** Fetch source media only after its shared optional pack has been enabled. */ export async function fetchOptionalMediaAsset(url: string): Promise { if (!combatMediaPackReady()) throw new Error("Original spell media is not installed."); return fetch(resolveContentUrl(url), { cache: "no-store" }); } /** Download and verify every file owned by the original combat-media pack. */ export async function downloadCombatMediaPack( onProgress?: ProgressListener, ): Promise { const publishProgress = () => { const state = getContentManagerSnapshot(); onProgress?.({ completed: state.downloadedBytes, total: Math.max(1, state.totalBytes), }); }; const unsubscribe = subscribeContentManager(publishProgress); try { await installContentPack(COMBAT_MEDIA_CONTENT_PACK_ID); publishProgress(); const pack = getContentManagerSnapshot().manifest?.packs.find((candidate) => ( candidate.id === COMBAT_MEDIA_CONTENT_PACK_ID )); return { fileCount: pack?.fileCount ?? 0 }; } finally { unsubscribe(); } } export async function removeCombatMediaPack(): Promise { await removeContentPack(COMBAT_MEDIA_CONTENT_PACK_ID); } export function combatMediaPackReady(): boolean { return contentPackIsUsable(COMBAT_MEDIA_CONTENT_PACK_ID); }