Decompiled source of SkinwalkerByTihi v1.0.0

BepInEx\plugins\SkinwalkerByTihi\SkinwalkerByTihi.dll

Decompiled 8 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;

[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("SkinwalkerByTihi")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+ca1470329f0a369121a9c909b55f935a31e9e07d")]
[assembly: AssemblyProduct("SkinwalkerByTihi")]
[assembly: AssemblyTitle("SkinwalkerByTihi")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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 SkinwalkerByTihi
{
	internal static class ClipManager
	{
		private readonly struct Entry
		{
			public readonly ulong PlayerId;

			public readonly int SeqNum;

			public readonly AudioClip Clip;

			public Entry(ulong pid, int seq, AudioClip clip)
			{
				PlayerId = pid;
				SeqNum = seq;
				Clip = clip;
			}
		}

		private static readonly object _lock = new object();

		private static readonly List<Entry> _clips = new List<Entry>();

		private static readonly Dictionary<ulong, int> _nextSeq = new Dictionary<ulong, int>();

		public static void AddClip(ulong playerId, AudioClip clip)
		{
			lock (_lock)
			{
				if (!_nextSeq.TryGetValue(playerId, out var value))
				{
					value = 0;
				}
				_nextSeq[playerId] = value + 1;
				_clips.Add(new Entry(playerId, value, clip));
				while (_clips.Count > SkinwalkerConfig.MaxStoredClips.Value)
				{
					Object.Destroy((Object)(object)_clips[0].Clip);
					_clips.RemoveAt(0);
				}
			}
		}

		public static AudioClip? GetRandomClipFromPlayer(ulong playerId)
		{
			lock (_lock)
			{
				List<AudioClip> list = new List<AudioClip>();
				foreach (Entry clip in _clips)
				{
					if (clip.PlayerId == playerId)
					{
						list.Add(clip.Clip);
					}
				}
				if (list.Count == 0)
				{
					return null;
				}
				return list[Random.Range(0, list.Count)];
			}
		}

		public static ulong? GetRandomPlayerId()
		{
			lock (_lock)
			{
				if (_clips.Count == 0)
				{
					return null;
				}
				return _clips[Random.Range(0, _clips.Count)].PlayerId;
			}
		}

		public static bool HasAny()
		{
			lock (_lock)
			{
				return _clips.Count > 0;
			}
		}

		public static void Clear()
		{
			lock (_lock)
			{
				foreach (Entry clip in _clips)
				{
					Object.Destroy((Object)(object)clip.Clip);
				}
				_clips.Clear();
				_nextSeq.Clear();
			}
		}
	}
	internal class EnemyVoiceComponent : MonoBehaviour
	{
		private EnemyAI? _enemy;

		private NetworkObject? _netObj;

		private AudioSource? _voiceSource;

		private float _nextPlayTime;

		private void Start()
		{
			_enemy = ((Component)this).GetComponent<EnemyAI>();
			_netObj = ((Component)this).GetComponent<NetworkObject>();
			_voiceSource = ((Component)this).gameObject.AddComponent<AudioSource>();
			_voiceSource.spatialBlend = 1f;
			_voiceSource.minDistance = 2f;
			_voiceSource.maxDistance = 30f;
			_voiceSource.rolloffMode = (AudioRolloffMode)1;
			_voiceSource.playOnAwake = false;
			ScheduleNextPlay();
		}

		private void Update()
		{
			if (SkinwalkerConfig.Enabled.Value && !(Time.time < _nextPlayTime))
			{
				ScheduleNextPlay();
				TryTriggerPlayback();
			}
		}

		private void TryTriggerPlayback()
		{
			if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsHost && !((Object)(object)_enemy == (Object)null) && !_enemy.isEnemyDead && !((Object)(object)_netObj == (Object)null) && ClipManager.HasAny())
			{
				ulong? randomPlayerId = ClipManager.GetRandomPlayerId();
				if (randomPlayerId.HasValue)
				{
					SkinwalkerNetworkHandler.SendPlayVoice(_netObj.NetworkObjectId, randomPlayerId.Value);
				}
			}
		}

		public void PlayClip(AudioClip clip)
		{
			if (!((Object)(object)_voiceSource == (Object)null))
			{
				_voiceSource.PlayOneShot(clip, SkinwalkerConfig.ClampedVol);
			}
		}

		private void ScheduleNextPlay()
		{
			float num = Random.Range(SkinwalkerConfig.MinSecs, SkinwalkerConfig.MaxSecs);
			_nextPlayTime = Time.time + num;
		}
	}
	internal static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "Tihi.SkinwalkerByTihi";

		public const string PLUGIN_NAME = "SkinwalkerByTihi";

		public const string PLUGIN_VERSION = "1.0.0";
	}
	[BepInPlugin("Tihi.SkinwalkerByTihi", "SkinwalkerByTihi", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log = null;

		private static readonly Harmony Harmony = new Harmony("Tihi.SkinwalkerByTihi");

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			SkinwalkerConfig.Init(((BaseUnityPlugin)this).Config);
			Harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"SkinwalkerByTihi v1.0.0 loaded!");
		}
	}
	internal static class SkinwalkerConfig
	{
		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<float> MinInterval;

		public static ConfigEntry<float> MaxInterval;

		public static ConfigEntry<float> MaxClipLength;

		public static ConfigEntry<int> MaxStoredClips;

		public static ConfigEntry<float> Volume;

		public static float ClipLength => Mathf.Clamp(MaxClipLength.Value, 1f, 10f);

		public static float MinSecs => Mathf.Max(MinInterval.Value, 1f);

		public static float MaxSecs => Mathf.Max(MaxInterval.Value, MinSecs + 1f);

		public static float ClampedVol => Mathf.Clamp01(Volume.Value);

		public static void Init(ConfigFile cfg)
		{
			Enabled = cfg.Bind<bool>("General", "Enabled", true, "Enable or disable the mod entirely.");
			MinInterval = cfg.Bind<float>("Timing", "MinInterval", 5f, "Minimum seconds between voice clips playing on any enemy.");
			MaxInterval = cfg.Bind<float>("Timing", "MaxInterval", 60f, "Maximum seconds between voice clips playing on any enemy.");
			MaxClipLength = cfg.Bind<float>("Recording", "MaxClipLength", 5f, "Length of each recorded voice clip in seconds. Max allowed: 10.");
			MaxStoredClips = cfg.Bind<int>("Recording", "MaxStoredClips", 30, "How many voice clips to keep in memory across all players.");
			Volume = cfg.Bind<float>("Playback", "Volume", 1f, "Playback volume multiplier (0.0 – 1.0).");
		}
	}
	internal static class SkinwalkerNetworkHandler
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static HandleNamedMessageDelegate <0>__OnReceivePlayVoice;
		}

		private const string MsgPlayVoice = "SkinwalkerByTihi_PlayVoice";

		private static bool _registered;

		public static void Register()
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			if (_registered)
			{
				return;
			}
			NetworkManager singleton = NetworkManager.Singleton;
			if (((singleton != null) ? singleton.CustomMessagingManager : null) != null)
			{
				CustomMessagingManager customMessagingManager = NetworkManager.Singleton.CustomMessagingManager;
				object obj = <>O.<0>__OnReceivePlayVoice;
				if (obj == null)
				{
					HandleNamedMessageDelegate val = OnReceivePlayVoice;
					<>O.<0>__OnReceivePlayVoice = val;
					obj = (object)val;
				}
				customMessagingManager.RegisterNamedMessageHandler("SkinwalkerByTihi_PlayVoice", (HandleNamedMessageDelegate)obj);
				_registered = true;
				Plugin.Log.LogInfo((object)"[Skinwalker] Network handler registered.");
			}
		}

		public static void Unregister()
		{
			if (!_registered)
			{
				return;
			}
			NetworkManager singleton = NetworkManager.Singleton;
			if (singleton != null)
			{
				CustomMessagingManager customMessagingManager = singleton.CustomMessagingManager;
				if (customMessagingManager != null)
				{
					customMessagingManager.UnregisterNamedMessageHandler("SkinwalkerByTihi_PlayVoice");
				}
			}
			_registered = false;
		}

		public static void SendPlayVoice(ulong enemyNetworkId, ulong sourcePlayerId)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: 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_003d: 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)
			if (NetworkManager.Singleton.IsHost)
			{
				HandlePlayVoice(enemyNetworkId, sourcePlayerId);
				FastBufferWriter val = default(FastBufferWriter);
				((FastBufferWriter)(ref val))..ctor(16, (Allocator)2, -1);
				((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref enemyNetworkId, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref sourcePlayerId, default(ForPrimitives));
				NetworkManager.Singleton.CustomMessagingManager.SendNamedMessageToAll("SkinwalkerByTihi_PlayVoice", val, (NetworkDelivery)2);
				((FastBufferWriter)(ref val)).Dispose();
			}
		}

		private static void OnReceivePlayVoice(ulong senderId, FastBufferReader reader)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			ulong enemyNetworkId = default(ulong);
			((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref enemyNetworkId, default(ForPrimitives));
			ulong sourcePlayerId = default(ulong);
			((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref sourcePlayerId, default(ForPrimitives));
			HandlePlayVoice(enemyNetworkId, sourcePlayerId);
		}

		private static void HandlePlayVoice(ulong enemyNetworkId, ulong sourcePlayerId)
		{
			if (!NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(enemyNetworkId, out var value))
			{
				return;
			}
			EnemyVoiceComponent component = ((Component)value).GetComponent<EnemyVoiceComponent>();
			if ((Object)(object)component == (Object)null)
			{
				return;
			}
			AudioClip randomClipFromPlayer = ClipManager.GetRandomClipFromPlayer(sourcePlayerId);
			if ((Object)(object)randomClipFromPlayer == (Object)null)
			{
				ulong? randomPlayerId = ClipManager.GetRandomPlayerId();
				if (randomPlayerId.HasValue)
				{
					randomClipFromPlayer = ClipManager.GetRandomClipFromPlayer(randomPlayerId.Value);
				}
			}
			if ((Object)(object)randomClipFromPlayer != (Object)null)
			{
				component.PlayClip(randomClipFromPlayer);
			}
		}
	}
	internal class VoiceRecorder : MonoBehaviour
	{
		private readonly ConcurrentQueue<float[]> _audioQueue = new ConcurrentQueue<float[]>();

		private readonly List<float> _accumulated = new List<float>();

		private int _sampleRate;

		private int _channels;

		public ulong PlayerClientId { get; set; }

		private void Start()
		{
			_sampleRate = AudioSettings.outputSampleRate;
		}

		private void OnAudioFilterRead(float[] data, int channels)
		{
			if (SkinwalkerConfig.Enabled.Value)
			{
				_channels = channels;
				float[] array = new float[data.Length];
				data.CopyTo(array, 0);
				_audioQueue.Enqueue(array);
			}
		}

		private void Update()
		{
			if (_sampleRate != 0 && _channels != 0)
			{
				float[] result;
				while (_audioQueue.TryDequeue(out result))
				{
					_accumulated.AddRange(result);
				}
				int num = (int)(SkinwalkerConfig.ClipLength * (float)_sampleRate) * _channels;
				while (_accumulated.Count >= num)
				{
					float[] array = _accumulated.GetRange(0, num).ToArray();
					_accumulated.RemoveRange(0, num);
					int num2 = num / _channels;
					AudioClip val = AudioClip.Create($"voice_{PlayerClientId}", num2, _channels, _sampleRate, false);
					val.SetData(array, 0);
					ClipManager.AddClip(PlayerClientId, val);
					Plugin.Log.LogDebug((object)$"[Skinwalker] Recorded clip from player {PlayerClientId} ({num2} samples)");
				}
				int num3 = num * 4;
				if (_accumulated.Count > num3)
				{
					_accumulated.RemoveRange(0, _accumulated.Count - num3);
				}
			}
		}
	}
}
namespace SkinwalkerByTihi.Patches
{
	[HarmonyPatch(typeof(EnemyAI), "Start")]
	internal static class EnemyPatch
	{
		private static void Postfix(EnemyAI __instance)
		{
			if (SkinwalkerConfig.Enabled.Value && !((Object)(object)((Component)__instance).GetComponent<EnemyVoiceComponent>() != (Object)null))
			{
				((Component)__instance).gameObject.AddComponent<EnemyVoiceComponent>();
			}
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "Start")]
	internal static class NetworkRegisterPatch
	{
		private static void Postfix()
		{
			SkinwalkerNetworkHandler.Register();
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "OnDestroy")]
	internal static class NetworkUnregisterPatch
	{
		private static void Postfix()
		{
			SkinwalkerNetworkHandler.Unregister();
			ClipManager.Clear();
		}
	}
	[HarmonyPatch(typeof(PlayerControllerB), "Start")]
	internal static class PlayerVoicePatch
	{
		[CompilerGenerated]
		private sealed class <AttachWhenReady>d__1 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public PlayerControllerB player;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <AttachWhenReady>d__1(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					break;
				case 1:
					<>1__state = -1;
					break;
				}
				if ((Object)(object)player.currentVoiceChatAudioSource == (Object)null)
				{
					<>2__current = null;
					<>1__state = 1;
					return true;
				}
				if ((Object)(object)player == (Object)(object)GameNetworkManager.Instance?.localPlayerController)
				{
					return false;
				}
				if ((Object)(object)((Component)player.currentVoiceChatAudioSource).GetComponent<VoiceRecorder>() != (Object)null)
				{
					return false;
				}
				((Component)player.currentVoiceChatAudioSource).gameObject.AddComponent<VoiceRecorder>().PlayerClientId = player.playerClientId;
				Plugin.Log.LogInfo((object)$"[Skinwalker] VoiceRecorder attached to player {player.playerUsername} (id={player.playerClientId})");
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private static void Postfix(PlayerControllerB __instance)
		{
			((MonoBehaviour)__instance).StartCoroutine(AttachWhenReady(__instance));
		}

		[IteratorStateMachine(typeof(<AttachWhenReady>d__1))]
		private static IEnumerator AttachWhenReady(PlayerControllerB player)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <AttachWhenReady>d__1(0)
			{
				player = player
			};
		}
	}
}