Files
2025-03-12 12:56:27 +00:00

286 lines
13 KiB
C#

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<Identifier> 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<SoundPrefab> WaterAmbienceIn = new PrefabSelector<SoundPrefab>();
public readonly static PrefabSelector<SoundPrefab> WaterAmbienceOut = new PrefabSelector<SoundPrefab>();
public readonly static PrefabSelector<SoundPrefab> WaterAmbienceMoving = new PrefabSelector<SoundPrefab>();
public readonly static PrefabSelector<SoundPrefab> StartupSound = new PrefabSelector<SoundPrefab>();
private readonly static List<SoundPrefab> flowSounds = new List<SoundPrefab>();
public static IReadOnlyList<SoundPrefab> FlowSounds => flowSounds;
private readonly static List<SoundPrefab> splashSounds = new List<SoundPrefab>();
public static IReadOnlyList<SoundPrefab> SplashSounds => splashSounds;
public readonly static ImmutableDictionary<Identifier, Type> TagToDerivedPrefab;
private readonly static ImmutableDictionary<Type, PrefabCollectionHandler> derivedPrefabCollections;
private readonly static ImmutableDictionary<Identifier, PrefabSelector<SoundPrefab>> prefabSelectors;
private readonly static ImmutableDictionary<Identifier, List<SoundPrefab>> prefabsWithTag;
public readonly static PrefabCollection<SoundPrefab> Prefabs;
static SoundPrefab()
{
var types = ReflectionUtils.GetDerivedNonAbstract<SoundPrefab>();
//types.ForEach(t => t.GetProperties(BindingFlags.Public | BindingFlags.Static));
TagToDerivedPrefab = types.SelectMany(t =>
t.GetCustomAttribute<TagNames>().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<SoundPrefab>));
prefabSelectors = prefabSelectorFields.Select(f => (f.Name.ToIdentifier(), (PrefabSelector<SoundPrefab>)f.GetValue(null))).ToImmutableDictionary();
var prefabsOfTagName = typeof(SoundPrefab).GetFields(BindingFlags.Static | BindingFlags.NonPublic)
.Where(f => f.FieldType == typeof(List<SoundPrefab>));
prefabsWithTag = prefabsOfTagName.Select(f => (f.Name.Substring(0, f.Name.Length-6).ToIdentifier(), (List<SoundPrefab>)f.GetValue(null))).ToImmutableDictionary();
Prefabs = new PrefabCollection<SoundPrefab>(
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 readonly float Volume;
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);
Volume = element.GetAttributeFloat(nameof(Volume), 1.0f);
}
public bool IsPlaying()
{
return Sound.IsPlaying();
}
public override void Dispose()
{
Sound?.Dispose(); Sound = null;
}
}
[TagNames("damagesound")]
class DamageSound : SoundPrefab
{
public readonly static PrefabCollection<DamageSound> DamageSoundPrefabs = new PrefabCollection<DamageSound>();
//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<BackgroundMusic> BackgroundMusicPrefabs = new PrefabCollection<BackgroundMusic>();
public readonly Identifier Type;
public readonly bool DuckVolume;
public readonly Vector2 IntensityRange;
public readonly bool MuteIntensityTracks;
public readonly float? ForceIntensityTrack;
public readonly bool StartFromRandomTime;
public readonly bool ContinueFromPreviousTime;
public int PreviousTime;
/// <summary>
/// The music is forced to play at least for this long when it triggers, even if the situation changes and makes the music no longer suitable.
/// </summary>
public readonly float MinimumPlayDuration;
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);
}
StartFromRandomTime = element.GetAttributeBool(nameof(StartFromRandomTime), false);
ContinueFromPreviousTime = element.GetAttributeBool(nameof(ContinueFromPreviousTime), false);
MinimumPlayDuration = element.GetAttributeFloat(nameof(MinimumPlayDuration), 0.0f);
}
}
[TagNames("guisound")]
class GUISound : SoundPrefab
{
//public readonly static Dictionary<GUISoundType, List<GUISound>> GUISoundsByType = new Dictionary<GUISoundType, List<GUISound>>();
public readonly static PrefabCollection<GUISound> GUISoundPrefabs = new PrefabCollection<GUISound>();
public readonly GUISoundType Type;
public GUISound(ContentXElement element, SoundsFile file) : base(element, file)
{
Type = element.GetAttributeEnum("guisoundtype", GUISoundType.UIMessage);
}
}
}