using System; using System.Collections.Generic; using DatReaderWriter.DBObjs; using DRWSound = DatReaderWriter.Enums.Sound; namespace AcDream.Core.Audio; /// /// Probabilistic entry picker over a retail . /// /// /// Each key in /// SoundTable.Sounds maps to a list of /// items each carrying a /// probability weight. Retail picks one entry per trigger by rolling the /// cumulative distribution โ€” that's how footsteps sound slightly /// different each step, how weapon swings have 3 swoosh variants, etc. /// /// /// /// r05 ยง4: the picker samples a uniform random in [0,1) and walks the /// entries accumulating probabilities; the first entry whose running total /// exceeds the sample wins. If all probabilities sum to < 1, the /// remaining mass means "silence" โ€” the call returns null. If probabilities /// sum to > 1 the picker still works correctly (it clamps on the last /// entry). /// /// public static class SoundCookbook { /// /// Pick one entry from a sound's variant list, weighted by probability. /// Returns null when the rolled sample falls into the "silence" /// remainder of the distribution (probability sum < 1). /// public static DatReaderWriter.Types.SoundEntry? Roll( IReadOnlyList entries, Random rng) { ArgumentNullException.ThrowIfNull(entries); ArgumentNullException.ThrowIfNull(rng); if (entries.Count == 0) return null; if (entries.Count == 1) return entries[0]; float sample = (float)rng.NextDouble(); float cum = 0f; for (int i = 0; i < entries.Count; i++) { cum += Math.Max(0f, entries[i].Probability); if (sample < cum) return entries[i]; } // Fell past the last entry โ€” either probabilities sum to >1 (return // last) or < 1 and we rolled into the "silence" tail (return null). float total = 0f; for (int i = 0; i < entries.Count; i++) total += Math.Max(0f, entries[i].Probability); return total > 0.999f ? entries[entries.Count - 1] : null; } /// /// Convenience lookup: given a SoundTable + a retail /// key (e.g. Swoosh1, Footstep1), roll the /// entry list and return the winning entry. Returns null if: /// /// has no mapping for /// . /// The mapping's entry list is empty. /// The probability roll hits the silence /// tail. /// /// public static DatReaderWriter.Types.SoundEntry? Roll( SoundTable table, DRWSound sound, Random rng) { ArgumentNullException.ThrowIfNull(table); if (!table.Sounds.TryGetValue(sound, out var soundData)) return null; return Roll(soundData.Entries, rng); } }