using System.IO; namespace CodeImp.DoomBuilder.ZDoom { public class AmbientSoundInfo { #region ================== Enums public enum AmbientType { NONE, POINT, SURROUND, WORLD } public enum AmbientMode { NONE, CONTINUOUS, RANDOM, PERIODIC } #endregion #region ================== Variables private string soundname; private string sounddescription; private int index = -1; private AmbientType type = AmbientType.NONE; private AmbientMode mode = AmbientMode.NONE; private float volume = 1.0f; private float attenuation = 1.0f; private float minsecs; private float maxsecs; private float secs; // Editor sound radii private float minradius; private float maxradius; #endregion #region ================== Properties public string SoundName { get { return soundname; } } public string SoundDescription { get { return sounddescription; } } public int Index { get { return index; } } // Ambient sound index // Sound settings public AmbientType AmbientSoundType { get { return type; } } public AmbientMode AmbientSoundMode { get { return mode; } } public float Volume { get { return volume; } } // Can be set when AmbientType == POINT public float Attenuation { get { return attenuation; } } // Used when AmbientMode == RANDOM public float SecondsMin { get { return minsecs; } } public float SecondsMax { get { return maxsecs; } } // Used when AmbientMode == PERIODIC public float Seconds { get { return secs; } } // Editor sound radii public float MinimumRadius { get { return minradius; } } public float MaximumRadius { get { return maxradius; } } #endregion #region ================== Methods internal bool Setup(SndInfoParser parser) { // Read index if(!parser.ReadSignedInt(ref index)) { // Not numeric! parser.ReportError("Expected $ambient value"); return false; } // Check index //INFO: Up to 64 ambient sounds can be used in the Doom map format and 256 in Hexen format. UDMF maps have no limit. //TODO: can index be zero/negative in UDMF? if(General.Map.DOOM && (index < 1 || index > 64)) { parser.ReportError("$ambient must be in [1..64] range"); return false; } if(General.Map.HEXEN && (index < 1 || index > 256)) { parser.ReportError("$ambient must be in [1..256] range"); return false; } // Read name if(!parser.SkipWhitespace(true)) return false; soundname = parser.StripTokenQuotes(parser.ReadToken(false)); if(string.IsNullOrEmpty(soundname)) { parser.ReportError("Expected $ambient value"); return false; } // Next token can be either [type] or ... if (!parser.SkipWhitespace(true)) return false; string token = parser.ReadToken(false).ToLowerInvariant(); // Can be [type] if(token == "point" || token == "surround" || token == "world") { // Next token may be attenuation... if(token == "point") { if(!parser.SkipWhitespace(false)) return false; string next = parser.ReadToken(false); if(!parser.ReadSignedFloat(next, ref attenuation) || attenuation < 0f) { // Rewind so this structure can be read again parser.DataStream.Seek(-next.Length - 1, SeekOrigin.Current); } } // Store type switch(token) { case "point": type = AmbientType.POINT; break; case "surround": type = AmbientType.SURROUND; break; case "world": type = AmbientType.WORLD; break; } // Read next token if(!parser.SkipWhitespace(false)) return false; token = parser.ReadToken(false).ToLowerInvariant(); } // Sould be if(token == "continuous" || token == "random" || token == "periodic") { // Next 2 tokens must be minsecs and maxsecs if(token == "random") { if(!parser.SkipWhitespace(false)) return false; if(!parser.ReadSignedFloat(ref minsecs) || minsecs < 0f) { parser.ReportError("Expected $ambient value"); return false; } if(!parser.SkipWhitespace(false)) return false; if(!parser.ReadSignedFloat(ref maxsecs) || maxsecs < 0f) { parser.ReportError("Expected $ambient value"); return false; } } // Next token must be secs else if(token == "periodic") { if(!parser.SkipWhitespace(false)) return false; if(!parser.ReadSignedFloat(ref secs) || secs < 0f) { parser.ReportError("Expected $ambient value"); return false; } } // Store mode switch(token) { case "continuous": mode = AmbientMode.CONTINUOUS; break; case "random": mode = AmbientMode.RANDOM; break; case "periodic": mode = AmbientMode.PERIODIC; break; } } else { parser.ReportError("Expected ambient sound or [type]"); return false; } // Read volume if(!parser.SkipWhitespace(false)) return false; if(!parser.ReadSignedFloat(ref volume) || volume < 0f) { parser.ReportError("Expected ambient sound value"); return false; } // There can be multiple different ambient sounds with the same sound name, so build a description containing the index // and the name to differentiate them. See https://github.com/jewalky/UltimateDoomBuilder/issues/390 sounddescription = index.ToString() + ": " + soundname + " (" + type.ToString().ToLowerInvariant(); if (type == AmbientType.POINT) sounddescription += ", attenuation: " + attenuation; if (mode == AmbientMode.CONTINUOUS) sounddescription += ", " + mode.ToString().ToLowerInvariant(); else if (mode == AmbientMode.RANDOM) sounddescription += ", every " + minsecs + " to " + maxsecs + " seconds"; else if (mode == AmbientMode.PERIODIC) sounddescription += ", every " + secs + " seconds"; sounddescription += ", " + volume * 100 + "% volume)"; return true; } internal void SetupSound(SoundInfo info) { // Store radii minradius = info.MinimumDistance / info.Attenuation; if(info.Rolloff == SoundInfo.RolloffType.LOG) { // Calculate from RolloffFactor maxradius = info.MinimumDistance + info.RolloffFactor * info.MinimumDistance; } else { maxradius = info.MaximumDistance; } maxradius /= info.Attenuation; } #endregion } }