Files
DndGamePlayer/app/renderer/control/poisonCloudSfx.ts
T
Ivan Fontosh 20c838da7d feat(effects): вода, облако яда, луч света; пульт и окна демонстрации
- Поле: вода (сплошная заливка по штриху, превью кистью), туман/огонь/дождь без изменений логики.

- Действия: облако яда (частицы, круглая текстура, звук oblako-yada.mp3, длительность как у трека), луч света и заморозка со звуками из public/.

- Пульт: инструменты воды и яда, синхрон SFX, тесты панели и ластика.

- Окно управления: дочернее от окна просмотра (Z-order).

- Типы эффектов, effectsStore prune, hit-test ластика.

Made-with: Cursor
2026-04-20 11:03:57 +08:00

60 lines
2.2 KiB
TypeScript

/** Звук и длительность эффекта «Облако яда» (`public/oblako-yada.mp3`). */
const POISON_CLOUD_SFX_VOLUME = 0.92;
/** Запас, если метаданные не прочитались. */
const DEFAULT_POISON_CLOUD_LIFE_MS = 1600;
export function poisonCloudEffectSoundUrl(): string {
return new URL('oblako-yada.mp3', window.location.href).href;
}
let cachedPoisonCloudSfxDurationMs: number | null = null;
/** Длительность трека в мс (кэш после первого чтения метаданных). */
export function getPoisonCloudSfxDurationMs(): Promise<number> {
if (cachedPoisonCloudSfxDurationMs !== null) {
return Promise.resolve(cachedPoisonCloudSfxDurationMs);
}
const url = poisonCloudEffectSoundUrl();
return new Promise((resolve) => {
const a = new Audio();
const done = (ms: number): void => {
cachedPoisonCloudSfxDurationMs = ms;
a.removeAttribute('src');
resolve(ms);
};
a.addEventListener('loadedmetadata', () => {
const d = a.duration;
done(Number.isFinite(d) && d > 0 ? Math.round(d * 1000) : DEFAULT_POISON_CLOUD_LIFE_MS);
});
a.addEventListener('error', () => done(DEFAULT_POISON_CLOUD_LIFE_MS));
a.src = url;
a.load();
});
}
/** Длительность визуала = длительности файла (с разумными пределами). */
export async function getPoisonCloudEffectLifeMs(): Promise<number> {
const raw = await getPoisonCloudSfxDurationMs();
return Math.min(60_000, Math.max(600, raw));
}
/**
* Воспроизведение с подгонкой скорости: фактическое время звука по часам ≈ `lifeMs`,
* чтобы совпасть с анимацией.
*/
export async function playPoisonCloudEffectSound(lifeMs: number): Promise<void> {
try {
const rawMs = await getPoisonCloudSfxDurationMs();
const target = Math.max(200, lifeMs);
const rate = Math.max(0.25, Math.min(4, rawMs / target));
const el = new Audio(poisonCloudEffectSoundUrl());
el.volume = POISON_CLOUD_SFX_VOLUME;
el.playbackRate = rate;
void el.play().catch(() => undefined);
} catch {
/* ignore */
}
}