fed5674468
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>
64 lines
2.4 KiB
TypeScript
64 lines
2.4 KiB
TypeScript
/** Звук и длительность эффекта «Взрыв» (`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 */
|
||
}
|
||
}
|