using Barotrauma.Extensions; using Barotrauma.IO; using Barotrauma.Sounds; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Reflection; using System.Text; using System.Xml.Linq; namespace Barotrauma { public class TagNames : Attribute { public readonly ImmutableHashSet Names; public TagNames(params string[] names) { Names = names.Select(n => n.ToIdentifier()).ToImmutableHashSet(); } } class SoundPrefab : Prefab { private class PrefabCollectionHandler { public readonly object Collection; public readonly MethodInfo AddMethod; public readonly MethodInfo RemoveMethod; public readonly MethodInfo SortAllMethod; public readonly MethodInfo AddOverrideFileMethod; public readonly MethodInfo RemoveOverrideFileMethod; public void Add(SoundPrefab p, bool isOverride) { AddMethod.Invoke(Collection, new object[] { p, isOverride }); } public void Remove(SoundPrefab p) { RemoveMethod.Invoke(Collection, new object[] { p }); } public void AddOverrideFile(ContentFile file) { AddOverrideFileMethod.Invoke(Collection, new object[] { file }); } public void RemoveOverrideFile(ContentFile file) { RemoveOverrideFileMethod.Invoke(Collection, new object[] { file }); } public void SortAll() { SortAllMethod.Invoke(Collection, null); } public PrefabCollectionHandler(Type type) { var collectionField = type.GetField($"{type.Name}Prefabs", BindingFlags.Public | BindingFlags.Static); if (collectionField is null) { throw new InvalidOperationException($"Couldn't determine PrefabCollection for {type.Name}"); } Collection = collectionField.GetValue(null) ?? throw new InvalidOperationException($"PrefabCollection for {type.Name} was null"); AddMethod = Collection.GetType().GetMethod("Add", BindingFlags.Public | BindingFlags.Instance); RemoveMethod = Collection.GetType().GetMethod("Remove", BindingFlags.Public | BindingFlags.Instance); AddOverrideFileMethod = Collection.GetType().GetMethod("AddOverrideFile", BindingFlags.Public | BindingFlags.Instance); RemoveOverrideFileMethod = Collection.GetType().GetMethod("RemoveOverrideFile", BindingFlags.Public | BindingFlags.Instance); SortAllMethod = Collection.GetType().GetMethod("SortAll", BindingFlags.Public | BindingFlags.Instance); } } public readonly static PrefabSelector WaterAmbienceIn = new PrefabSelector(); public readonly static PrefabSelector WaterAmbienceOut = new PrefabSelector(); public readonly static PrefabSelector WaterAmbienceMoving = new PrefabSelector(); public readonly static PrefabSelector StartupSound = new PrefabSelector(); private readonly static List flowSounds = new List(); public static IReadOnlyList FlowSounds => flowSounds; private readonly static List splashSounds = new List(); public static IReadOnlyList SplashSounds => splashSounds; public readonly static ImmutableDictionary TagToDerivedPrefab; private readonly static ImmutableDictionary derivedPrefabCollections; private readonly static ImmutableDictionary> prefabSelectors; private readonly static ImmutableDictionary> prefabsWithTag; public readonly static PrefabCollection Prefabs; static SoundPrefab() { var types = ReflectionUtils.GetDerivedNonAbstract(); //types.ForEach(t => t.GetProperties(BindingFlags.Public | BindingFlags.Static)); TagToDerivedPrefab = types.SelectMany(t => t.GetCustomAttribute().Names.Select(n => (n, t))).ToImmutableDictionary(); derivedPrefabCollections = types.Select(t => (t, new PrefabCollectionHandler(t))).ToImmutableDictionary(); var prefabSelectorFields = typeof(SoundPrefab).GetFields(BindingFlags.Public | BindingFlags.Static) .Where(f => f.FieldType == typeof(PrefabSelector)); prefabSelectors = prefabSelectorFields.Select(f => (f.Name.ToIdentifier(), (PrefabSelector)f.GetValue(null))).ToImmutableDictionary(); var prefabsOfTagName = typeof(SoundPrefab).GetFields(BindingFlags.Static | BindingFlags.NonPublic) .Where(f => f.FieldType == typeof(List)); prefabsWithTag = prefabsOfTagName.Select(f => (f.Name.Substring(0, f.Name.Length-6).ToIdentifier(), (List)f.GetValue(null))).ToImmutableDictionary(); Prefabs = new PrefabCollection( onAdd: (SoundPrefab p, bool isOverride) => { if (derivedPrefabCollections.ContainsKey(p.GetType())) { derivedPrefabCollections[p.GetType()].Add(p, isOverride); } if (prefabSelectors.ContainsKey(p.ElementName)) { prefabSelectors[p.ElementName].Add(p, isOverride); } UpdateSoundsWithTag(); }, onRemove: (SoundPrefab p) => { if (derivedPrefabCollections.ContainsKey(p.GetType())) { derivedPrefabCollections[p.GetType()].Remove(p); } if (prefabSelectors.ContainsKey(p.ElementName)) { prefabSelectors[p.ElementName].RemoveIfContains(p); } UpdateSoundsWithTag(); SoundPlayer.DisposeDisabledMusic(); }, onSort: () => { derivedPrefabCollections.Values.ForEach(h => h.SortAll()); prefabSelectors.Values.ForEach(h => h.Sort()); }, onAddOverrideFile: (file) => {derivedPrefabCollections.Values.ForEach(h => h.AddOverrideFile(file)); }, onRemoveOverrideFile: (file) => { derivedPrefabCollections.Values.ForEach(h => h.RemoveOverrideFile(file)); } ); } private static void UpdateSoundsWithTag() { foreach (var tag in prefabsWithTag.Keys) { var list = prefabsWithTag[tag]; list.Clear(); list.AddRange(Prefabs.Where(p => p.ElementName == tag)); list.Sort((p1, p2) => { if (p1.ContentFile.ContentPackage.Index < p2.ContentFile.ContentPackage.Index) { return -1; } if (p1.ContentFile.ContentPackage.Index > p2.ContentFile.ContentPackage.Index) { return 1; } if (p2.Element.ComesAfter(p1.Element)) { return -1; } if (p1.Element.ComesAfter(p2.Element)) { return 1; } return 0; }); } } protected override Identifier DetermineIdentifier(XElement element) { Identifier id = base.DetermineIdentifier(element); if (id.IsEmpty) { if (id.IsEmpty) { id = Path.GetFileNameWithoutExtension(element.GetAttributeStringUnrestricted("path", "")).ToIdentifier(); } if (id.IsEmpty) { id = Path.GetFileNameWithoutExtension(element.GetAttributeStringUnrestricted("file", "")).ToIdentifier(); } if (!id.IsEmpty) { id = $"{element.Name}_{id}".ToIdentifier(); string damageSoundType = element.GetAttributeString("damagesoundtype", ""); if (!damageSoundType.IsNullOrEmpty()) { id = $"{id}_{damageSoundType}".ToIdentifier(); } string musicType = element.GetAttributeString("type", ""); if (!musicType.IsNullOrEmpty()) { id = $"{id}_{musicType}".ToIdentifier(); } } } return id; } public readonly ContentPath SoundPath; public readonly ContentXElement Element; public readonly Identifier ElementName; public Sound Sound { get; private set; } public SoundPrefab(ContentXElement element, SoundsFile file, bool stream = false) : base(file, element) { SoundPath = element.GetAttributeContentPath("file") ?? ContentPath.Empty; Element = element; ElementName = element.NameAsIdentifier(); Sound = GameMain.SoundManager.LoadSound(element, stream: stream); } public bool IsPlaying() { return Sound.IsPlaying(); } public override void Dispose() { Sound?.Dispose(); Sound = null; } } [TagNames("damagesound")] class DamageSound : SoundPrefab { public readonly static PrefabCollection DamageSoundPrefabs = new PrefabCollection(); //the range of inflicted damage where the sound can be played //(10.0f, 30.0f) would be played when the inflicted damage is between 10 and 30 public readonly Vector2 DamageRange; public readonly Identifier DamageType; public readonly Identifier RequiredTag; public bool IgnoreMuffling; public DamageSound(ContentXElement element, SoundsFile file) : base(element, file) { DamageRange = element.GetAttributeVector2("damagerange", Vector2.Zero); DamageType = element.GetAttributeIdentifier("damagesoundtype", "None"); IgnoreMuffling = element.GetAttributeBool("ignoremuffling", false); RequiredTag = element.GetAttributeIdentifier("requiredtag", ""); } } [TagNames("music")] class BackgroundMusic : SoundPrefab { public readonly static PrefabCollection BackgroundMusicPrefabs = new PrefabCollection(); public readonly Identifier Type; public readonly bool DuckVolume; public readonly float Volume; public readonly Vector2 IntensityRange; public readonly bool MuteIntensityTracks; public readonly float? ForceIntensityTrack; public readonly bool StartFromRandomTime; public readonly bool ContinueFromPreviousTime; public int PreviousTime; public BackgroundMusic(ContentXElement element, SoundsFile file) : base(element, file, stream: true) { Type = element.GetAttributeIdentifier(nameof(Type), ""); IntensityRange = element.GetAttributeVector2(nameof(IntensityRange), new Vector2(0.0f, 100.0f)); DuckVolume = element.GetAttributeBool(nameof(DuckVolume), false); MuteIntensityTracks = element.GetAttributeBool(nameof(MuteIntensityTracks), false); if (element.GetAttribute(nameof(ForceIntensityTrack)) != null) { ForceIntensityTrack = element.GetAttributeFloat(nameof(ForceIntensityTrack), 0.0f); } Volume = element.GetAttributeFloat(nameof(Volume), 1.0f); StartFromRandomTime = element.GetAttributeBool(nameof(StartFromRandomTime), false); ContinueFromPreviousTime = element.GetAttributeBool(nameof(ContinueFromPreviousTime), false); } } [TagNames("guisound")] class GUISound : SoundPrefab { //public readonly static Dictionary> GUISoundsByType = new Dictionary>(); public readonly static PrefabCollection GUISoundPrefabs = new PrefabCollection(); public readonly GUISoundType Type; public GUISound(ContentXElement element, SoundsFile file) : base(element, file) { Type = element.GetAttributeEnum("guisoundtype", GUISoundType.UIMessage); } } }