using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("MonsterSounds")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+f18fe521039d284dd6e4262b8d4841b8e5f9798a")] [assembly: AssemblyProduct("MonsterSounds")] [assembly: AssemblyTitle("MonsterSounds")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace MonsterSounds { internal sealed class MonsterRule { public string NameMatch = ""; public string SoundFile = ""; public float Volume = 0.35f; public float StepDistance = 1.8f; public float MinWalkSpeed = 0.4f; public float PitchVariation = 0.2f; public float MaxHearingDistance = 25f; } internal sealed class MonsterRuleSet { private readonly List _rules = new List(); public int Count => _rules.Count; private static string Template { get { using Stream stream = typeof(MonsterRuleSet).Assembly.GetManifestResourceStream("monsters.txt"); using StreamReader streamReader = new StreamReader(stream); return streamReader.ReadToEnd(); } } public static MonsterRuleSet LoadOrCreate(string path) { if (!File.Exists(path)) { File.WriteAllText(path, Template); Plugin.Log.LogInfo((object)("Created example rules file at " + path)); } MonsterRuleSet monsterRuleSet = new MonsterRuleSet(); string[] array = File.ReadAllLines(path); foreach (string text in array) { string text2 = text.Trim(); if (text2.Length == 0 || text2.StartsWith("#")) { continue; } string[] array2 = text2.Split('|'); if (array2.Length < 2) { Plugin.Log.LogWarning((object)("Skipping malformed rule (need at least name|soundfile): " + text)); continue; } MonsterRule monsterRule = new MonsterRule { NameMatch = array2[0].Trim(), SoundFile = array2[1].Trim() }; if (monsterRule.NameMatch.Length == 0 || monsterRule.SoundFile.Length == 0) { Plugin.Log.LogWarning((object)("Skipping rule with empty name or sound file: " + text)); continue; } monsterRule.Volume = ParseFloat(array2, 2, monsterRule.Volume, 0f, 1f); monsterRule.StepDistance = ParseFloat(array2, 3, monsterRule.StepDistance, 0.1f, 20f); monsterRule.MinWalkSpeed = ParseFloat(array2, 4, monsterRule.MinWalkSpeed, 0f, 10f); monsterRule.PitchVariation = ParseFloat(array2, 5, monsterRule.PitchVariation, 0f, 0.9f); monsterRule.MaxHearingDistance = ParseFloat(array2, 6, monsterRule.MaxHearingDistance, 2f, 200f); monsterRuleSet._rules.Add(monsterRule); } return monsterRuleSet; } public MonsterRule? Match(string enemyName) { foreach (MonsterRule rule in _rules) { if (enemyName.IndexOf(rule.NameMatch, StringComparison.OrdinalIgnoreCase) >= 0) { return rule; } } return null; } public IEnumerable AllSoundFiles() { return _rules.Select((MonsterRule r) => r.SoundFile).Distinct(StringComparer.OrdinalIgnoreCase); } private static float ParseFloat(string[] parts, int index, float fallback, float min, float max) { if (parts.Length <= index) { return fallback; } if (!float.TryParse(parts[index].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return fallback; } return Math.Max(min, Math.Min(max, result)); } } [HarmonyPatch(typeof(EnemyParent), "Setup")] internal static class EnemyParentSetupPatch { private static void Postfix(string ___enemyName, Enemy ___Enemy) { string text = ___enemyName ?? ""; if (Plugin.Debug.Value) { Plugin.Log.LogInfo((object)("Enemy setup: '" + text + "'")); } MonsterRule monsterRule = Plugin.Rules.Match(text); if (monsterRule == null) { return; } if ((Object)(object)___Enemy == (Object)null) { Plugin.Log.LogWarning((object)("'" + text + "' matched '" + monsterRule.NameMatch + "' but its Enemy reference was null; no sound attached.")); return; } GameObject gameObject = ((Component)___Enemy).gameObject; if (!((Object)(object)gameObject.GetComponent() != (Object)null)) { gameObject.AddComponent().Init(___Enemy, text, monsterRule); Plugin.Log.LogInfo((object)("Attached '" + monsterRule.SoundFile + "' to '" + text + "' (matched '" + monsterRule.NameMatch + "').")); } } } [BepInPlugin("MonsterSounds", "MonsterSounds", "1.0.0")] public class Plugin : BaseUnityPlugin { internal static Plugin Instance; internal static ManualLogSource Log; internal static SoundLibrary Sounds; internal static MonsterRuleSet Rules; internal static string SoundsDir; internal static ConfigEntry HostOnly; internal static ConfigEntry Debug; private void Awake() { //IL_00b7: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; HostOnly = ((BaseUnityPlugin)this).Config.Bind("General", "HostOnly", false, "If true, only the host (or singleplayer) hears the sounds. If false, every player who has the mod hears the monsters near them."); Debug = ((BaseUnityPlugin)this).Config.Bind("General", "Debug", true, "Log every enemy's name at spawn and each sound played. Turn off once it's working."); SoundsDir = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), "sounds"); Directory.CreateDirectory(SoundsDir); Rules = MonsterRuleSet.LoadOrCreate(Path.Combine(SoundsDir, "monsters.txt")); Sounds = new SoundLibrary(); Sounds.Preload(Rules.AllSoundFiles()); new Harmony("MonsterSounds").PatchAll(); Log.LogInfo((object)string.Format("{0} {1} loaded with {2} rule(s).", "MonsterSounds", "1.0.0", Rules.Count)); } } internal sealed class SoundLibrary { private readonly Dictionary _clips = new Dictionary(StringComparer.OrdinalIgnoreCase); public void Preload(IEnumerable files) { foreach (string file in files) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(Load(file)); } } public bool TryGetClip(string file, out AudioClip clip) { return _clips.TryGetValue(file, out clip); } private IEnumerator Load(string file) { string text = Path.Combine(Plugin.SoundsDir, file); if (!File.Exists(text)) { Plugin.Log.LogWarning((object)("Sound file not found: " + text)); yield break; } UnityWebRequest req = UnityWebRequestMultimedia.GetAudioClip("file://" + text, GetAudioType(file)); try { yield return req.SendWebRequest(); if ((int)req.result != 1) { Plugin.Log.LogWarning((object)("Failed to load '" + file + "': " + req.error)); yield break; } AudioClip content = DownloadHandlerAudioClip.GetContent(req); ((Object)content).name = file; _clips[file] = content; Plugin.Log.LogInfo((object)("Loaded sound '" + file + "'.")); } finally { ((IDisposable)req)?.Dispose(); } } private static AudioType GetAudioType(string file) { switch (Path.GetExtension(file).ToLowerInvariant()) { case ".wav": return (AudioType)20; case ".ogg": return (AudioType)14; case ".mp3": return (AudioType)13; case ".aiff": case ".aif": return (AudioType)2; default: return (AudioType)0; } } } internal sealed class WalkSoundEmitter : MonoBehaviour { private const float TeleportStep = 1.5f; private static readonly FieldInfo? CenterField = AccessTools.Field(typeof(Enemy), "CenterTransform"); private string _name = ""; private MonsterRule _rule; private Transform _follow; private AudioSource _source; private Vector3 _lastPos; private float _distance; private int _playCount; private bool _ready; public void Init(Enemy enemy, string name, MonsterRule rule) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) _name = name; _rule = rule; object? obj = CenterField?.GetValue(enemy); Transform val = (Transform)((obj is Transform) ? obj : null); _follow = (((Object)(object)val != (Object)null) ? val : ((Component)enemy).transform); GameObject val2 = new GameObject("MonsterSounds_Audio"); val2.transform.SetParent(_follow, false); val2.transform.localPosition = Vector3.zero; _source = val2.AddComponent(); _source.playOnAwake = false; _source.spatialBlend = 1f; _source.dopplerLevel = 0f; _source.rolloffMode = (AudioRolloffMode)1; _source.minDistance = 1.5f; _source.maxDistance = _rule.MaxHearingDistance; _lastPos = _follow.position; _ready = true; if (Plugin.Debug.Value) { Plugin.Log.LogInfo((object)("WalkSoundEmitter ready for '" + _name + "' -> '" + _rule.SoundFile + "', tracking '" + ((Object)_follow).name + "'.")); } } private void Update() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) if (!_ready || (Plugin.HostOnly.Value && !SemiFunc.IsMasterClientOrSingleplayer()) || (Object)(object)_follow == (Object)null) { return; } Vector3 position = _follow.position; Vector3 val = position - _lastPos; _lastPos = position; Vector2 val2 = new Vector2(val.x, val.z); float magnitude = ((Vector2)(ref val2)).magnitude; if (magnitude > 1.5f) { _distance = 0f; } else if (!(magnitude / Mathf.Max(Time.deltaTime, 0.0001f) < _rule.MinWalkSpeed)) { _distance += magnitude; while (_distance >= _rule.StepDistance) { _distance -= _rule.StepDistance; PlayStep(); } } } private void PlayStep() { if (Plugin.Sounds.TryGetClip(_rule.SoundFile, out AudioClip clip) && !_source.isPlaying) { float pitchVariation = _rule.PitchVariation; _source.pitch = 1f + Random.Range(0f - pitchVariation, pitchVariation); _source.PlayOneShot(clip, _rule.Volume); if (Plugin.Debug.Value && _playCount < 5) { _playCount++; Plugin.Log.LogInfo((object)("played '" + _rule.SoundFile + "' (" + _name + ")")); } } } private void OnDestroy() { if ((Object)(object)_source != (Object)null) { Object.Destroy((Object)(object)((Component)_source).gameObject); } } } public static class MyPluginInfo { public const string PLUGIN_GUID = "MonsterSounds"; public const string PLUGIN_NAME = "MonsterSounds"; public const string PLUGIN_VERSION = "1.0.0"; } }