Decompiled source of PhantomWhisper v1.1.0

PhantomWhisper.dll

Decompiled 23 minutes ago
using System;
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;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 PhantomWhisper
{
	internal static class GhostVoice
	{
		private static GameObject _voiceObject;

		private static AudioDistortionFilter _distortion;

		private static AudioEchoFilter _echo;

		private static AudioChorusFilter _chorus;

		public static bool Applied { get; private set; }

		public static int PitchPlayerId { get; private set; } = -1;

		public static void Apply(PlayerControllerB whisperer)
		{
			AudioSource currentVoiceChatAudioSource = whisperer.currentVoiceChatAudioSource;
			if (!((Object)(object)currentVoiceChatAudioSource == (Object)null))
			{
				if (!Applied || (Object)(object)_voiceObject != (Object)(object)((Component)currentVoiceChatAudioSource).gameObject)
				{
					Remove();
					_voiceObject = ((Component)currentVoiceChatAudioSource).gameObject;
					_distortion = _voiceObject.AddComponent<AudioDistortionFilter>();
					_echo = _voiceObject.AddComponent<AudioEchoFilter>();
					_chorus = _voiceObject.AddComponent<AudioChorusFilter>();
					PitchPlayerId = (int)whisperer.playerClientId;
					Applied = true;
					Plugin.Log.LogDebug((object)$"Ghost voice attached to player {PitchPlayerId}.");
				}
				VoiceStyle currentStyle = PhantomConfig.CurrentStyle;
				((Behaviour)_distortion).enabled = currentStyle.Distortion > 0.001f;
				_distortion.distortionLevel = currentStyle.Distortion;
				((Behaviour)_echo).enabled = currentStyle.EchoDecay > 0.001f;
				_echo.delay = currentStyle.EchoDelayMs;
				_echo.decayRatio = currentStyle.EchoDecay;
				_echo.dryMix = 1f;
				_echo.wetMix = 0.6f;
				((Behaviour)_chorus).enabled = currentStyle.Chorus > 0.001f;
				_chorus.dryMix = 0.5f;
				_chorus.wetMix1 = 0.5f * currentStyle.Chorus;
				_chorus.wetMix2 = 0.5f * currentStyle.Chorus;
				_chorus.wetMix3 = 0.5f * currentStyle.Chorus;
				_chorus.delay = 40f;
				_chorus.rate = 0.8f;
				_chorus.depth = 0.03f;
				SoundManager instance = SoundManager.Instance;
				if ((Object)(object)instance != (Object)null)
				{
					instance.SetPlayerPitch(currentStyle.Pitch, PitchPlayerId);
				}
			}
		}

		public static void Remove()
		{
			if (Applied)
			{
				int pitchPlayerId = PitchPlayerId;
				Applied = false;
				PitchPlayerId = -1;
				if ((Object)(object)_distortion != (Object)null)
				{
					Object.Destroy((Object)(object)_distortion);
				}
				if ((Object)(object)_echo != (Object)null)
				{
					Object.Destroy((Object)(object)_echo);
				}
				if ((Object)(object)_chorus != (Object)null)
				{
					Object.Destroy((Object)(object)_chorus);
				}
				_distortion = null;
				_echo = null;
				_chorus = null;
				_voiceObject = null;
				SoundManager instance = SoundManager.Instance;
				if ((Object)(object)instance != (Object)null && instance.playerVoicePitches != null && pitchPlayerId >= 0 && pitchPlayerId < instance.playerVoicePitches.Length)
				{
					instance.SetPlayerPitch(instance.playerVoicePitches[pitchPlayerId], pitchPlayerId);
				}
				RefreshVanillaVoiceState();
			}
		}

		public static void RefreshVanillaVoiceState()
		{
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				instance.UpdatePlayerVoiceEffects();
			}
		}
	}
	internal static class ModInfo
	{
		public const string Guid = "phantomwhisper.lethalcompany";

		public const string Name = "PhantomWhisper";

		public const string Version = "1.1.0";
	}
	[HarmonyPatch]
	internal static class Patches
	{
		private static PlayerControllerB LocalPlayer
		{
			get
			{
				GameNetworkManager instance = GameNetworkManager.Instance;
				if (!((Object)(object)instance != (Object)null))
				{
					return null;
				}
				return instance.localPlayerController;
			}
		}

		[HarmonyPatch(typeof(PlayerVoiceIngameSettings), "LateUpdate")]
		[HarmonyPostfix]
		private static void ApplyWhisperOverrides(PlayerVoiceIngameSettings __instance)
		{
			if (!WhisperNetwork.Active)
			{
				return;
			}
			PlayerControllerB localPlayer = LocalPlayer;
			PlayerControllerB whisperer = WhisperNetwork.GetWhisperer();
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)whisperer == (Object)null || (Object)(object)whisperer == (Object)(object)localPlayer || (Object)(object)__instance != (Object)(object)whisperer.currentVoiceChatIngameSettings)
			{
				return;
			}
			AudioSource currentVoiceChatAudioSource = whisperer.currentVoiceChatAudioSource;
			if ((Object)(object)currentVoiceChatAudioSource == (Object)null || whisperer.voicePlayerState == null)
			{
				return;
			}
			if (WhisperNetwork.IsTarget(localPlayer))
			{
				__instance.set2D = true;
				currentVoiceChatAudioSource.spatialBlend = 0f;
				whisperer.voicePlayerState.Volume = PhantomConfig.WhisperVolume.Value;
				AudioLowPassFilter component = ((Component)currentVoiceChatAudioSource).GetComponent<AudioLowPassFilter>();
				if ((Object)(object)component != (Object)null)
				{
					((Behaviour)component).enabled = false;
				}
				AudioHighPassFilter component2 = ((Component)currentVoiceChatAudioSource).GetComponent<AudioHighPassFilter>();
				if ((Object)(object)component2 != (Object)null)
				{
					((Behaviour)component2).enabled = false;
				}
			}
			else
			{
				whisperer.voicePlayerState.Volume = 0f;
			}
		}

		[HarmonyPatch(typeof(SoundManager), "SetPlayerPitch")]
		[HarmonyPrefix]
		private static void OverridePlayerPitch(ref float pitch, int playerObjNum)
		{
			if (GhostVoice.Applied && playerObjNum == GhostVoice.PitchPlayerId)
			{
				pitch = PhantomConfig.CurrentStyle.Pitch;
			}
		}
	}
	internal enum VoicePreset
	{
		Ghost,
		Demon,
		Banshee,
		Custom
	}
	internal struct VoiceStyle
	{
		public float Pitch;

		public float Distortion;

		public float EchoDelayMs;

		public float EchoDecay;

		public float Chorus;
	}
	internal static class PhantomConfig
	{
		public static ConfigEntry<Key> WhisperKey;

		public static ConfigEntry<bool> ToggleMode;

		public static ConfigEntry<float> WhisperVolume;

		public static ConfigEntry<VoicePreset> Preset;

		public static ConfigEntry<float> CustomPitch;

		public static ConfigEntry<float> CustomDistortion;

		public static ConfigEntry<float> CustomEchoDelayMs;

		public static ConfigEntry<float> CustomEchoDecay;

		public static ConfigEntry<float> CustomChorus;

		public static VoiceStyle CurrentStyle => Preset.Value switch
		{
			VoicePreset.Demon => new VoiceStyle
			{
				Pitch = 0.5f,
				Distortion = 0.55f,
				EchoDelayMs = 180f,
				EchoDecay = 0.35f,
				Chorus = 0f
			}, 
			VoicePreset.Banshee => new VoiceStyle
			{
				Pitch = 1.6f,
				Distortion = 0.25f,
				EchoDelayMs = 400f,
				EchoDecay = 0.65f,
				Chorus = 0.8f
			}, 
			VoicePreset.Custom => new VoiceStyle
			{
				Pitch = CustomPitch.Value,
				Distortion = CustomDistortion.Value,
				EchoDelayMs = CustomEchoDelayMs.Value,
				EchoDecay = CustomEchoDecay.Value,
				Chorus = CustomChorus.Value
			}, 
			_ => new VoiceStyle
			{
				Pitch = 0.7f,
				Distortion = 0.15f,
				EchoDelayMs = 300f,
				EchoDecay = 0.55f,
				Chorus = 0.5f
			}, 
		};

		public static void Bind(ConfigFile cfg)
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Expected O, but got Unknown
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Expected O, but got Unknown
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Expected O, but got Unknown
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Expected O, but got Unknown
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Expected O, but got Unknown
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Expected O, but got Unknown
			WhisperKey = cfg.Bind<Key>("Host", "WhisperKey", (Key)22, "Key the HOST holds to whisper to the player they are spectating. Any UnityEngine.InputSystem.Key name works (H, B, F, Backquote, ...). Only the host's key matters.");
			ToggleMode = cfg.Bind<bool>("Host", "ToggleMode", false, "false = hold the key to whisper. true = press once to start, press again to stop (handy if you use voice activation).");
			WhisperVolume = cfg.Bind<float>("Listeners", "WhisperVolume", 1f, new ConfigDescription("How loud the whisper is for the player it is aimed at (applies on that player's side).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			Preset = cfg.Bind<VoicePreset>("Voice", "Preset", VoicePreset.Ghost, "Voice effect preset. Use Custom to take the values below instead.");
			CustomPitch = cfg.Bind<float>("Voice", "CustomPitch", 0.7f, new ConfigDescription("Custom preset: pitch (0.5 = one octave down, 2 = one octave up).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 2f), Array.Empty<object>()));
			CustomDistortion = cfg.Bind<float>("Voice", "CustomDistortion", 0.15f, new ConfigDescription("Custom preset: distortion amount.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			CustomEchoDelayMs = cfg.Bind<float>("Voice", "CustomEchoDelayMs", 300f, new ConfigDescription("Custom preset: echo delay in milliseconds.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 1000f), Array.Empty<object>()));
			CustomEchoDecay = cfg.Bind<float>("Voice", "CustomEchoDecay", 0.55f, new ConfigDescription("Custom preset: echo decay (0 disables the echo).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.95f), Array.Empty<object>()));
			CustomChorus = cfg.Bind<float>("Voice", "CustomChorus", 0.5f, new ConfigDescription("Custom preset: chorus / shimmer amount (0 disables it).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
		}
	}
	[BepInPlugin("phantomwhisper.lethalcompany", "PhantomWhisper", "1.1.0")]
	public class Plugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log;

		private Harmony _harmony;

		private bool _hostWhispering;

		private bool _toggled;

		private ulong _currentTarget;

		private float _lastHeartbeat;

		private float _lastHint = -100f;

		private bool _mutedHost;

		private static PlayerControllerB LocalPlayer
		{
			get
			{
				GameNetworkManager instance = GameNetworkManager.Instance;
				if (!((Object)(object)instance != (Object)null))
				{
					return null;
				}
				return instance.localPlayerController;
			}
		}

		private void Awake()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			PhantomConfig.Bind(((BaseUnityPlugin)this).Config);
			_harmony = new Harmony("phantomwhisper.lethalcompany");
			_harmony.PatchAll(typeof(Patches));
			Log.LogInfo((object)string.Format("{0} {1} loaded. Host whisper key: {2}", "PhantomWhisper", "1.1.0", PhantomConfig.WhisperKey.Value));
		}

		private void OnDestroy()
		{
			GhostVoice.Remove();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void Update()
		{
			WhisperNetwork.EnsureRegistered();
			WhisperNetwork.ExpireIfStale();
			if ((Object)(object)StartOfRound.Instance == (Object)null)
			{
				WhisperNetwork.ResetState();
			}
			HostTick();
			ListenerTick();
		}

		private void HostTick()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			NetworkManager singleton = NetworkManager.Singleton;
			PlayerControllerB localPlayer = LocalPlayer;
			Keyboard current = Keyboard.current;
			if (!((Object)(object)singleton != (Object)null) || !singleton.IsHost || !((Object)(object)localPlayer != (Object)null) || current == null || (int)PhantomConfig.WhisperKey.Value == 0)
			{
				_hostWhispering = false;
				_toggled = false;
				return;
			}
			bool flag = localPlayer.isTypingChat || localPlayer.inTerminalMenu || ((Object)(object)localPlayer.quickMenuManager != (Object)null && localPlayer.quickMenuManager.isMenuOpen);
			KeyControl val = current[PhantomConfig.WhisperKey.Value];
			bool flag2 = !flag && ((ButtonControl)val).wasPressedThisFrame;
			bool flag3;
			if (PhantomConfig.ToggleMode.Value)
			{
				if (flag)
				{
					_toggled = false;
				}
				else if (flag2)
				{
					_toggled = !_toggled;
				}
				flag3 = _toggled;
			}
			else
			{
				flag3 = !flag && ((ButtonControl)val).isPressed;
			}
			ulong targetId;
			bool flag4 = TryGetSpectatedTarget(localPlayer, out targetId);
			if (flag3 && !flag4)
			{
				_toggled = false;
				if (flag2)
				{
					Hint("You need to be dead and spectating a player to whisper to them.");
				}
			}
			if (flag3 && flag4)
			{
				if (!_hostWhispering || targetId != _currentTarget || Time.unscaledTime - _lastHeartbeat >= 1f)
				{
					_currentTarget = targetId;
					_hostWhispering = true;
					_lastHeartbeat = Time.unscaledTime;
					WhisperNetwork.Broadcast(active: true, localPlayer.playerClientId, targetId);
				}
			}
			else if (_hostWhispering)
			{
				_hostWhispering = false;
				WhisperNetwork.Broadcast(active: false, localPlayer.playerClientId, _currentTarget);
			}
		}

		private static bool TryGetSpectatedTarget(PlayerControllerB local, out ulong targetId)
		{
			targetId = 0uL;
			if (!local.isPlayerDead)
			{
				return false;
			}
			PlayerControllerB spectatedPlayerScript = local.spectatedPlayerScript;
			if ((Object)(object)spectatedPlayerScript == (Object)null || (Object)(object)spectatedPlayerScript == (Object)(object)local)
			{
				return false;
			}
			targetId = spectatedPlayerScript.playerClientId;
			return true;
		}

		private void Hint(string text)
		{
			if (!(Time.unscaledTime - _lastHint < 8f) && !((Object)(object)HUDManager.Instance == (Object)null))
			{
				_lastHint = Time.unscaledTime;
				HUDManager.Instance.DisplayTip("Phantom Whisper", text, false, false, "PhantomWhisper_Hint");
			}
		}

		private void ListenerTick()
		{
			PlayerControllerB localPlayer = LocalPlayer;
			PlayerControllerB whisperer = WhisperNetwork.GetWhisperer();
			bool flag = (Object)(object)localPlayer != (Object)null && (Object)(object)whisperer != (Object)null && (Object)(object)whisperer != (Object)(object)localPlayer;
			if (flag && WhisperNetwork.IsTarget(localPlayer))
			{
				GhostVoice.Apply(whisperer);
				_mutedHost = false;
				return;
			}
			if (GhostVoice.Applied)
			{
				GhostVoice.Remove();
			}
			bool flag2 = flag;
			if (_mutedHost && !flag2)
			{
				GhostVoice.RefreshVanillaVoiceState();
			}
			_mutedHost = flag2;
		}
	}
	internal static class WhisperNetwork
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static HandleNamedMessageDelegate <0>__OnMessage;
		}

		private const string MessageName = "PhantomWhisper.State";

		public const float HeartbeatSeconds = 1f;

		private const float TimeoutSeconds = 3.5f;

		private static float _lastHeard;

		private static CustomMessagingManager _registeredOn;

		public static bool Active { get; private set; }

		public static ulong WhispererId { get; private set; }

		public static ulong TargetId { get; private set; }

		public static void EnsureRegistered()
		{
			//IL_004d: 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: Expected O, but got Unknown
			NetworkManager singleton = NetworkManager.Singleton;
			CustomMessagingManager val = (((Object)(object)singleton != (Object)null) ? singleton.CustomMessagingManager : null);
			if (val == null)
			{
				_registeredOn = null;
			}
			else if (val != _registeredOn)
			{
				val.UnregisterNamedMessageHandler("PhantomWhisper.State");
				object obj = <>O.<0>__OnMessage;
				if (obj == null)
				{
					HandleNamedMessageDelegate val2 = OnMessage;
					<>O.<0>__OnMessage = val2;
					obj = (object)val2;
				}
				val.RegisterNamedMessageHandler("PhantomWhisper.State", (HandleNamedMessageDelegate)obj);
				_registeredOn = val;
				Plugin.Log.LogDebug((object)"Registered whisper message handler.");
			}
		}

		public static void ExpireIfStale()
		{
			if (Active && Time.unscaledTime - _lastHeard > 3.5f)
			{
				Plugin.Log.LogDebug((object)"Whisper state timed out, clearing.");
				Active = false;
			}
		}

		public static void ResetState()
		{
			Active = false;
		}

		public static PlayerControllerB GetWhisperer()
		{
			return GetPlayer(WhispererId);
		}

		public static bool IsTarget(PlayerControllerB player)
		{
			if (Active && (Object)(object)player != (Object)null)
			{
				return player.playerClientId == TargetId;
			}
			return false;
		}

		private static PlayerControllerB GetPlayer(ulong id)
		{
			if (!Active)
			{
				return null;
			}
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null || instance.allPlayerScripts == null || id >= (ulong)instance.allPlayerScripts.Length)
			{
				return null;
			}
			return instance.allPlayerScripts[id];
		}

		public unsafe static void Broadcast(bool active, ulong whispererId, ulong targetId)
		{
			//IL_0031: 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_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null || !singleton.IsHost || singleton.CustomMessagingManager == null)
			{
				return;
			}
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(17, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref whispererId, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref targetId, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe<bool>(ref active, default(ForPrimitives));
				singleton.CustomMessagingManager.SendNamedMessageToAll("PhantomWhisper.State", val, (NetworkDelivery)2);
			}
			finally
			{
				((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private static void OnMessage(ulong senderClientId, FastBufferReader reader)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			NetworkManager singleton = NetworkManager.Singleton;
			if (!((Object)(object)singleton == (Object)null) && senderClientId == 0L && !singleton.IsHost)
			{
				ulong whispererId = default(ulong);
				((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref whispererId, default(ForPrimitives));
				ulong targetId = default(ulong);
				((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref targetId, default(ForPrimitives));
				bool active = default(bool);
				((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref active, default(ForPrimitives));
				Active = active;
				WhispererId = whispererId;
				TargetId = targetId;
				_lastHeard = Time.unscaledTime;
			}
		}
	}
}