Decompiled source of MonsterSounds v1.0.0

MonsterSounds.dll

Decompiled 8 hours ago
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<MonsterRule> _rules = new List<MonsterRule>();

		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<string> AllSoundFiles()
		{
			return _rules.Select((MonsterRule r) => r.SoundFile).Distinct<string>(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<WalkSoundEmitter>() != (Object)null))
			{
				gameObject.AddComponent<WalkSoundEmitter>().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<bool> HostOnly;

		internal static ConfigEntry<bool> 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<bool>("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<bool>("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<string, AudioClip> _clips = new Dictionary<string, AudioClip>(StringComparer.OrdinalIgnoreCase);

		public void Preload(IEnumerable<string> 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<AudioSource>();
			_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";
	}
}