Files
healer-man/src/game/combatMediaPack.ts
T
2026-09-05 11:40:50 -04:00

71 lines
2.3 KiB
TypeScript

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<Response> {
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<Response> {
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<CombatMediaDownloadResult> {
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<void> {
await removeContentPack(COMBAT_MEDIA_CONTENT_PACK_ID);
}
export function combatMediaPackReady(): boolean {
return contentPackIsUsable(COMBAT_MEDIA_CONTENT_PACK_ID);
}