import { playMasterAudioCue } from "./combatAudio"; export type ManastormPortalCueSignal = "portal-coming-up" | "portal-ready"; export interface ManastormPortalAudioCue { readonly id: string; readonly signal: ManastormPortalCueSignal; readonly variant: "generic" | "single"; readonly alternate: boolean; readonly fileName: string; readonly url: string; } export interface ManastormClientCueRequest { readonly cueId: ManastormPortalCueId; readonly revision: number; } export interface ManastormAudioHandle { currentTime: number; preload: string; volume: number; pause: () => void; play: () => Promise | void; } export type ManastormAudioFactory = (url: string) => ManastormAudioHandle; export const MANASTORM_PORTAL_AUDIO_ROOT = "/assets/game/combat-effects/audio/manastorm"; function packagedPortalAudioUrl(fileName: string): string { return `${MANASTORM_PORTAL_AUDIO_ROOT}/${fileName}`; } export const MANASTORM_PORTAL_AUDIO_CUES = [ { id: "generic-portal-coming-up", signal: "portal-coming-up", variant: "generic", alternate: false, fileName: "Manastorm_BossKill_GenericPortalComingUp.wav", url: packagedPortalAudioUrl("Manastorm_BossKill_GenericPortalComingUp.wav"), }, { id: "single-portal-coming-up", signal: "portal-coming-up", variant: "single", alternate: false, fileName: "Manastorm_BossKill_1PortalComingUp.wav", url: packagedPortalAudioUrl("Manastorm_BossKill_1PortalComingUp.wav"), }, { id: "single-portal-coming-up-alt", signal: "portal-coming-up", variant: "single", alternate: true, fileName: "Manastorm_BossKill_1PortalComingUp_2.wav", url: packagedPortalAudioUrl("Manastorm_BossKill_1PortalComingUp_2.wav"), }, { id: "generic-portals-ready", signal: "portal-ready", variant: "generic", alternate: false, fileName: "Manastorm_BossKill_GenericPortalsReady.wav", url: packagedPortalAudioUrl("Manastorm_BossKill_GenericPortalsReady.wav"), }, { id: "generic-portals-ready-alt", signal: "portal-ready", variant: "generic", alternate: true, fileName: "Manastorm_BossKill_GenericPortalsReady_2.wav", url: packagedPortalAudioUrl("Manastorm_BossKill_GenericPortalsReady_2.wav"), }, ] as const satisfies readonly ManastormPortalAudioCue[]; export type ManastormPortalCueId = typeof MANASTORM_PORTAL_AUDIO_CUES[number]["id"]; export const MANASTORM_DEFAULT_PORTAL_READY_CUE_ID: ManastormPortalCueId = "generic-portals-ready"; const cueById = new Map( MANASTORM_PORTAL_AUDIO_CUES.map((cue) => [cue.id, cue]), ); let activeAudio: ManastormAudioHandle | null = null; export function manastormPortalAudioCue( cueId: ManastormPortalCueId, ): ManastormPortalAudioCue { const cue = cueById.get(cueId); if (!cue) throw new Error(`Unknown Manastorm client cue ${cueId}.`); return cue; } export function isManastormPortalCueId(value: string): value is ManastormPortalCueId { return cueById.has(value as ManastormPortalCueId); } export function manastormCueForStatusTransition( previousStatus: string, nextStatus: string, ): ManastormPortalCueId | null { return previousStatus !== "portal-ready" && nextStatus === "portal-ready" ? MANASTORM_DEFAULT_PORTAL_READY_CUE_ID : null; } function browserAudioFactory(url: string): ManastormAudioHandle { let stopPlayback: (() => void) | null = null; let stopped = false; const handle: ManastormAudioHandle = { currentTime: 0, preload: "auto", volume: 1, pause: () => { stopped = true; stopPlayback?.(); stopPlayback = null; }, play: async () => { stopped = false; const stop = await playMasterAudioCue(url, handle.volume); if (!stop) throw new Error("Web Audio is unavailable."); if (stopped) stop(); else stopPlayback = stop; }, }; return handle; } export async function playManastormClientCue( cueId: ManastormPortalCueId, audioFactory: ManastormAudioFactory = browserAudioFactory, ): Promise { const cue = manastormPortalAudioCue(cueId); try { activeAudio?.pause(); const audio = audioFactory(cue.url); audio.preload = "auto"; audio.volume = 0.72; audio.currentTime = 0; activeAudio = audio; await audio.play(); return true; } catch { activeAudio = null; return false; } } export function stopManastormClientCue(): void { activeAudio?.pause(); activeAudio = null; }