Files
DndGamePlayer/app/renderer/control/explosionSfx.ts
T
Ivan Fontosh fed5674468 feat(traps): activation VFX/SFX, explosion effect, and help section
Wire mimic/pit/arrow/laser media on activate, poison/explosion via effects, and document the scene editor and traps in Instructions.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-25 12:35:22 +08:00

64 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** Звук и длительность эффекта «Взрыв» (`public/explosion.mp3` + WebM с альфой). */
import { getEffectsSfxGain } from './effectsSfxGain';
const EXPLOSION_SFX_VOLUME = 0.95;
/** Запас, если метаданные не прочитались (длина webm ~3.97 с). */
const DEFAULT_EXPLOSION_LIFE_MS = 4200;
export function explosionEffectSoundUrl(): string {
return new URL('explosion.mp3', window.location.href).href;
}
/** VP8/VP9 WebM с `alpha_mode=1` — рендер через HTML `<video>`, не Pixi. */
export function explosionEffectVideoUrl(): string {
return new URL('vfx/explosion/aerial-debris-smoke.webm', window.location.href).href;
}
let cachedExplosionSfxDurationMs: number | null = null;
/** Длительность трека в мс (кэш после первого чтения метаданных). */
export function getExplosionSfxDurationMs(): Promise<number> {
if (cachedExplosionSfxDurationMs !== null) {
return Promise.resolve(cachedExplosionSfxDurationMs);
}
const url = explosionEffectSoundUrl();
return new Promise((resolve) => {
const a = new Audio();
const done = (ms: number): void => {
cachedExplosionSfxDurationMs = ms;
a.removeAttribute('src');
resolve(ms);
};
a.addEventListener('loadedmetadata', () => {
const d = a.duration;
done(Number.isFinite(d) && d > 0 ? Math.round(d * 1000) : DEFAULT_EXPLOSION_LIFE_MS);
});
a.addEventListener('error', () => done(DEFAULT_EXPLOSION_LIFE_MS));
a.src = url;
a.load();
});
}
/** Длительность визуала ≈ max(звук, webm), с разумными пределами. */
export async function getExplosionEffectLifeMs(): Promise<number> {
const sfxMs = await getExplosionSfxDurationMs();
const raw = Math.max(sfxMs, DEFAULT_EXPLOSION_LIFE_MS);
return Math.min(60_000, Math.max(600, raw));
}
export async function playExplosionEffectSound(lifeMs: number): Promise<void> {
try {
const rawMs = await getExplosionSfxDurationMs();
const target = Math.max(200, lifeMs);
const rate = Math.max(0.25, Math.min(4, rawMs / target));
const el = new Audio(explosionEffectSoundUrl());
el.volume = Math.max(0, Math.min(1, EXPLOSION_SFX_VOLUME * getEffectsSfxGain()));
el.playbackRate = rate;
void el.play().catch(() => undefined);
} catch {
/* ignore */
}
}