using System;
using AcDream.Core.Audio;
using DatReaderWriter.DBObjs;
using DRWSound = DatReaderWriter.Enums.Sound;
namespace AcDream.App.Audio;
///
/// Retail's interface sound bus — SoundManager::PlaySoundFromCenter @
/// 0x00550950 over ClientUISystem::GetUISoundTable @
/// 0x00563FB0.
///
///
/// "From centre" means pan 0 and GetAttenuation(0.0f, vol, &out, 0) —
/// distance zero, so the flat branch of the curve, and the effect volume
/// knob rather than the ambient one. Retail's separate
/// interface_sound_volume preference is registered and never read, so
/// there is deliberately no interface volume here either (AP-174).
///
///
///
/// The bank's DID is resolved from the dats by
/// rather than hard-coded, and the table itself is fetched lazily on first use
/// exactly as retail's GetUISoundTable caches it behind a null check.
///
///
public sealed class UiSoundController
{
private readonly OpenAlAudioEngine _engine;
private readonly DatSoundCache _cache;
private readonly ISoundRandom _rng;
private readonly uint _tableDid;
private SoundTable? _table;
private bool _tableMissing;
public UiSoundController(
OpenAlAudioEngine engine,
DatSoundCache cache,
uint tableDid,
ISoundRandom? rng = null)
{
_engine = engine ?? throw new ArgumentNullException(nameof(engine));
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_tableDid = tableDid;
_rng = rng ?? new SoundRandom();
}
/// The resolved bank DID, or 0 when the dats carry no chain.
public uint TableDid => _tableDid;
///
/// Play one interface slot. Returns false when the bank is absent, the slot
/// is unauthored, or the entry's probability gate says silence.
///
public bool Play(SoundId sound)
{
if (!_engine.IsAvailable || _tableDid == 0 || _tableMissing)
return false;
if (_table is null)
{
_table = _cache.GetSoundTable(_tableDid);
if (_table is null)
{
_tableMissing = true;
return false;
}
}
var entry = SoundCookbook.Select(_table, (DRWSound)sound, _rng);
if (entry is null)
return false;
uint waveId = (uint)entry.Id;
if (waveId == 0)
return false;
WaveData? wave = _cache.GetWave(waveId);
if (wave is null)
return false;
// PlaySoundFromCenter takes no volume argument, so the authored entry
// volume is the one that reaches the mixer.
return _engine.PlayUiWave(waveId, wave, entry.Volume);
}
///
/// Play the interface stinger for an AdminEnvirons change type — the
/// server-driven dungeon atmosphere (chanting, drums, whispers, thunder).
/// Codes retail has no case for, including 0x73/0x74 inside the
/// run, play nothing.
///
public bool PlayEnvironCue(uint changeType) =>
EnvironSoundCueMap.TryGetSound(changeType, out SoundId sound) && Play(sound);
}