Decompiled source of I miss my wife v1.0.0

plugins/com.github.GABRlEL.BBMissesHisWife.dll

Decompiled 3 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("com.github.GABRlEL.BBMissesHisWife")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("com.github.GABRlEL.BBMissesHisWife")]
[assembly: AssemblyTitle("BBMissesHisWife")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.0.0")]
[module: UnverifiableCode]
[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 BepInEx
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	internal sealed class BepInAutoPluginAttribute : Attribute
	{
		public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace BepInEx.Preloader.Core.Patching
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	internal sealed class PatcherAutoPluginAttribute : Attribute
	{
		public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace BBMissesHisWife
{
	[BepInPlugin("com.github.GABRlEL.BBMissesHisWife", "BBMissesHisWife", "0.1.0")]
	public class Plugin : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(Action_AskBingBong), "RunAction")]
		private static class Patch_ActionAskBingBong_RunAction
		{
			private static bool Prefix(Action_AskBingBong __instance)
			{
				int? targetResponseIndex = GetTargetResponseIndex(__instance);
				if (!targetResponseIndex.HasValue)
				{
					return true;
				}
				try
				{
					Item val = ItemField(__instance);
					if ((Object)(object)val == (Object)null || (Object)(object)((MonoBehaviourPun)val).photonView == (Object)null)
					{
						return true;
					}
					float num = LastAskedField.Invoke(__instance);
					bool flag = Time.time < num + 1f;
					((MonoBehaviourPun)val).photonView.RPC("Ask", (RpcTarget)0, new object[2] { targetResponseIndex.Value, flag });
					if (Time.time > num + 1f)
					{
						LastAskedField.Invoke(__instance) = Time.time;
					}
					return false;
				}
				catch (Exception arg)
				{
					Log.LogError((object)$"BBMissesHisWife RunAction patch failed, falling back to vanilla behavior: {arg}");
					return true;
				}
			}
		}

		[HarmonyPatch(typeof(Action_AskBingBong), "Ask")]
		private static class Patch_ActionAskBingBong_Ask
		{
			private static bool Prefix(Action_AskBingBong __instance, int index, bool spamming)
			{
				//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
				try
				{
					Item val = ItemField(__instance);
					if ((Object)(object)val == (Object)null || (Object)(object)val.holderCharacter == (Object)null)
					{
						return false;
					}
					bool isMasterClient = PhotonNetwork.IsMasterClient;
					bool flag = (Object)(object)((MonoBehaviourPun)val).photonView != (Object)null && ((MonoBehaviourPun)val).photonView.IsMine;
					int num = index;
					int? targetResponseIndex = GetTargetResponseIndex(__instance);
					if (_masterClientForceWifeForEveryone.Value && isMasterClient && !flag && targetResponseIndex.HasValue && num != targetResponseIndex.Value)
					{
						bool spamming2 = !_masterClientIgnoreSpamCooldown.Value && spamming;
						if (_masterClientForceAlsoAffectsHostView.Value)
						{
							index = targetResponseIndex.Value;
						}
						float num2 = Mathf.Max(0f, _masterClientBroadcastDelaySeconds.Value);
						if (_masterClientInterruptVanillaAudio.Value)
						{
							num2 = Mathf.Max(0.55f, num2);
						}
						((MonoBehaviour)__instance).StartCoroutine(SendForcedAskAfterDelay(((MonoBehaviourPun)val).photonView, targetResponseIndex.Value, spamming2, num2));
					}
					if ((_overrideIncomingRpc.Value || (_masterClientForceWifeForEveryone.Value && isMasterClient && _masterClientForceAlsoAffectsHostView.Value)) && targetResponseIndex.HasValue)
					{
						index = targetResponseIndex.Value;
					}
					if (_ignoreSpamCooldown.Value)
					{
						spamming = false;
					}
					if (_masterClientForceWifeForEveryone.Value && isMasterClient && targetResponseIndex.HasValue && _masterClientIgnoreSpamCooldown.Value)
					{
						spamming = false;
					}
					if ((Object)(object)__instance.squishAnim != (Object)null)
					{
						__instance.squishAnim.SetTrigger("Squish");
					}
					if ((Object)(object)SFX_Player.instance != (Object)null && (Object)(object)__instance.squeak != (Object)null)
					{
						SFX_Player.instance.PlaySFX(__instance.squeak, ((Component)__instance).transform.position, ((Component)__instance).transform, (SFX_Settings)null, 1f, false);
					}
					if ((Object)(object)__instance.subtitles != (Object)null)
					{
						((Component)__instance.subtitles).gameObject.SetActive(false);
					}
					if (_stopPreviousAskRoutines.Value)
					{
						StopTrackedCoroutine(__instance);
						StopTrackedSubtitleCoroutine(__instance);
					}
					if (_stopAudioImmediately.Value && (Object)(object)__instance.source != (Object)null)
					{
						__instance.source.Stop();
					}
					StartTrackedAskRoutine(__instance, index, spamming);
					return false;
				}
				catch (Exception arg)
				{
					Log.LogError((object)$"BBMissesHisWife Ask patch failed, falling back to vanilla behavior: {arg}");
					return true;
				}
			}
		}

		[CompilerGenerated]
		private sealed class <SendForcedAskAfterDelay>d__29 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public float delay;

			public PhotonView pv;

			public int forcedIndex;

			public bool spamming;

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

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

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

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

			private bool MoveNext()
			{
				//IL_002b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0035: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					if (delay > 0f)
					{
						<>2__current = (object)new WaitForSeconds(delay);
						<>1__state = 1;
						return true;
					}
					break;
				case 1:
					<>1__state = -1;
					break;
				}
				if ((Object)(object)pv != (Object)null)
				{
					pv.RPC("Ask", (RpcTarget)1, new object[2] { forcedIndex, spamming });
				}
				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 Harmony _harmony = null;

		private static ConfigEntry<int> _forceIndex = null;

		private static ConfigEntry<string> _subtitleKeywords = null;

		private static ConfigEntry<string> _clipKeywords = null;

		private static ConfigEntry<bool> _overrideIncomingRpc = null;

		private static ConfigEntry<bool> _ignoreSpamCooldown = null;

		private static ConfigEntry<bool> _verboseLogging = null;

		private static ConfigEntry<bool> _masterClientForceWifeForEveryone = null;

		private static ConfigEntry<bool> _masterClientForceAlsoAffectsHostView = null;

		private static ConfigEntry<bool> _masterClientIgnoreSpamCooldown = null;

		private static ConfigEntry<float> _masterClientBroadcastDelaySeconds = null;

		private static ConfigEntry<bool> _masterClientInterruptVanillaAudio = null;

		private static ConfigEntry<bool> _stopPreviousAskRoutines = null;

		private static ConfigEntry<bool> _stopAudioImmediately = null;

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

		private static bool _printedCandidatesThisSession;

		private static readonly MethodInfo? AskRoutineMethod = AccessTools.Method(typeof(Action_AskBingBong), "AskRoutine", (Type[])null, (Type[])null);

		private static readonly FieldRef<Action_AskBingBong, Coroutine> AskRoutineField = AccessTools.FieldRefAccess<Action_AskBingBong, Coroutine>("askRoutine");

		private static readonly FieldRef<Action_AskBingBong, Coroutine> SubtitleRoutineField = AccessTools.FieldRefAccess<Action_AskBingBong, Coroutine>("subtitleRoutine");

		private static readonly FieldRef<Action_AskBingBong, float> LastAskedField = AccessTools.FieldRefAccess<Action_AskBingBong, float>("lastAsked");

		private static readonly FieldRef<ItemActionBase, Item> ItemFieldBase = AccessTools.FieldRefAccess<ItemActionBase, Item>("item");

		public const string Id = "com.github.GABRlEL.BBMissesHisWife";

		internal static ManualLogSource Log { get; private set; } = null;


		public static string Name => "BBMissesHisWife";

		public static string Version => "0.1.0";

		private void Awake()
		{
			//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d1: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			_forceIndex = ((BaseUnityPlugin)this).Config.Bind<int>("General", "ForceResponseIndex", -1, "If >= 0, forcibly uses this response index. Leave at -1 to auto-detect.");
			_overrideIncomingRpc = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "OverrideIncomingRpc", false, "If true, overrides incoming Bing Bong RPCs too (client-side). Keep this OFF (default) if you want other players' Bing Bong lines to stay random/vanilla on your screen.");
			_ignoreSpamCooldown = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "IgnoreSpamCooldown", false, "If true, plays the line even when the game thinks you're spamming Bing Bong (within 1s).");
			_subtitleKeywords = ((BaseUnityPlugin)this).Config.Bind<string>("AutoDetect", "SubtitleKeywords", "wife,misses his wife", "Comma-separated keywords; if any match the Bing Bong subtitle text OR subtitle ID, that response is selected.");
			_clipKeywords = ((BaseUnityPlugin)this).Config.Bind<string>("AutoDetect", "ClipNameKeywords", "wife,misswife,misseswife", "Comma-separated keywords; if any match the AudioClip name, that response is selected.");
			_verboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "VerboseLogging", false, "If true, logs all Bing Bong responses (subtitle text + clip name) once per session to help you pick a ForceResponseIndex.");
			_masterClientForceWifeForEveryone = ((BaseUnityPlugin)this).Config.Bind<bool>("Host", "MasterClientForceWifeForEveryone", false, "If true AND you are the Photon Master Client, you will attempt to force OTHER players' Bing Bong results to 'wife' for everyone in the lobby (even unmodded clients).");
			_masterClientForceAlsoAffectsHostView = ((BaseUnityPlugin)this).Config.Bind<bool>("Host", "MasterClientForceAlsoAffectsHostView", true, "If true, when MasterClientForceWifeForEveryone is enabled and you are Master Client, your own screen will also show the forced 'wife' line when other players use Bing Bong.");
			_masterClientIgnoreSpamCooldown = ((BaseUnityPlugin)this).Config.Bind<bool>("Host", "MasterClientIgnoreSpamCooldown", true, "If true (recommended), the host-forced line will be sent with spamming=false so it actually plays (vanilla cancels audio/subtitles when spamming=true).");
			_masterClientInterruptVanillaAudio = ((BaseUnityPlugin)this).Config.Bind<bool>("Host", "InterruptVanillaAudio", true, "If true (recommended), the host-forced RPC is delayed so it can STOP the original random voice on unmodded clients before playing the forced 'wife' clip.");
			_masterClientBroadcastDelaySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Host", "BroadcastDelaySeconds", 2.6f, "Delay before host sends the forced RPC to other clients. Needs to be >2.6s to reliably interrupt the original on unmodded clients. Try 2.4–2.8 depending on latency.");
			_stopPreviousAskRoutines = ((BaseUnityPlugin)this).Config.Bind<bool>("Fixes", "StopPreviousAskRoutines", true, "If true, this plugin will stop the previous Bing Bong Ask coroutine before starting a new one (prevents local double-audio when multiple Ask RPCs arrive).");
			_stopAudioImmediately = ((BaseUnityPlugin)this).Config.Bind<bool>("Fixes", "StopAudioImmediately", true, "If true, immediately stops Bing Bong's AudioSource before starting the (new) AskRoutine on THIS client. Helps prevent local overlap.");
			_harmony = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID);
			_harmony.PatchAll(typeof(Plugin).Assembly);
			Log.LogInfo((object)("Plugin " + Name + " loaded. Nya~ \ud83d\udc3e"));
		}

		internal static int? GetTargetResponseIndex(Action_AskBingBong action)
		{
			if ((Object)(object)action == (Object)null || action.responses == null || action.responses.Length == 0)
			{
				return null;
			}
			int value = _forceIndex.Value;
			if (value >= 0 && value < action.responses.Length)
			{
				return value;
			}
			int instanceID = ((Object)action).GetInstanceID();
			if (CachedTargetIndexByInstanceId.TryGetValue(instanceID, out var value2) && value2 >= 0 && value2 < action.responses.Length)
			{
				return value2;
			}
			string[] array = SplitKeywords(_subtitleKeywords.Value);
			string[] array2 = SplitKeywords(_clipKeywords.Value);
			int num = -1;
			int num2 = 0;
			for (int i = 0; i < action.responses.Length; i++)
			{
				BingBongResponse val = action.responses[i];
				if (val == null)
				{
					continue;
				}
				int num3 = 0;
				string text = val.subtitleID ?? string.Empty;
				if (array.Length != 0 && ContainsAny(text, array))
				{
					num3 += 30;
				}
				string text2 = TryGetLocalizedText(text);
				if (!string.IsNullOrEmpty(text2) && !text2.StartsWith("LOC:", StringComparison.OrdinalIgnoreCase))
				{
					if (array.Length != 0 && ContainsAny(text2, array))
					{
						num3 += 120;
					}
					if (text2.IndexOf("wife", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						num3 += 10;
					}
				}
				string text3 = FirstClipName(val);
				if (!string.IsNullOrEmpty(text3))
				{
					if (array2.Length != 0 && ContainsAny(text3, array2))
					{
						num3 += 100;
					}
					if (text3.IndexOf("wife", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						num3 += 10;
					}
				}
				if (num3 > num2)
				{
					num2 = num3;
					num = i;
				}
			}
			if (_verboseLogging.Value && !_printedCandidatesThisSession)
			{
				_printedCandidatesThisSession = true;
				LogCandidates(action);
			}
			if (num >= 0 && num2 >= 80)
			{
				CachedTargetIndexByInstanceId[instanceID] = num;
				return num;
			}
			if (_verboseLogging.Value)
			{
				Log.LogWarning((object)"BBMissesHisWife couldn't confidently auto-detect the 'wife' response. Enable VerboseLogging and/or set ForceResponseIndex in the config (BepInEx/config/*.cfg).");
			}
			return null;
		}

		private static void LogCandidates(Action_AskBingBong action)
		{
			try
			{
				Log.LogInfo((object)"Bing Bong responses detected (index: subtitleID => localizedText | clipName):");
				for (int i = 0; i < action.responses.Length; i++)
				{
					BingBongResponse val = action.responses[i];
					if (val == null)
					{
						Log.LogInfo((object)$"  [{i}] <null>");
						continue;
					}
					string text = val.subtitleID ?? "";
					string text2 = TryGetLocalizedText(text);
					if (text2.Length > 140)
					{
						text2 = text2.Substring(0, 140) + "…";
					}
					string text3 = FirstClipName(val);
					Log.LogInfo((object)$"  [{i}] {text} => {text2} | {text3}");
				}
			}
			catch (Exception arg)
			{
				Log.LogDebug((object)$"Failed to log candidates: {arg}");
			}
		}

		private static string TryGetLocalizedText(string subtitleId)
		{
			if (string.IsNullOrWhiteSpace(subtitleId))
			{
				return string.Empty;
			}
			try
			{
				return LocalizedText.GetText(subtitleId, true) ?? string.Empty;
			}
			catch
			{
				return string.Empty;
			}
		}

		private static string FirstClipName(BingBongResponse resp)
		{
			try
			{
				AudioClip val = resp.sfx?.clips?.FirstOrDefault((Func<AudioClip, bool>)((AudioClip c) => (Object)(object)c != (Object)null));
				return ((Object)(object)val != (Object)null) ? ((Object)val).name : string.Empty;
			}
			catch
			{
				return string.Empty;
			}
		}

		private static string[] SplitKeywords(string raw)
		{
			return (from s in (raw ?? string.Empty).Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
				select s.Trim() into s
				where s.Length > 0
				select s).ToArray();
		}

		private static bool ContainsAny(string haystack, string[] needles)
		{
			if (string.IsNullOrEmpty(haystack) || needles.Length == 0)
			{
				return false;
			}
			foreach (string text in needles)
			{
				if (text.Length != 0 && haystack.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return true;
				}
			}
			return false;
		}

		[IteratorStateMachine(typeof(<SendForcedAskAfterDelay>d__29))]
		private static IEnumerator SendForcedAskAfterDelay(PhotonView pv, int forcedIndex, bool spamming, float delay)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <SendForcedAskAfterDelay>d__29(0)
			{
				pv = pv,
				forcedIndex = forcedIndex,
				spamming = spamming,
				delay = delay
			};
		}

		private static void StartTrackedAskRoutine(Action_AskBingBong action, int index, bool spamming)
		{
			if (AskRoutineMethod == null)
			{
				Log.LogWarning((object)"AskRoutine method not found via reflection; Bing Bong will not play audio/subtitles.");
			}
			else if (AskRoutineMethod.Invoke(action, new object[2] { index, spamming }) is IEnumerator enumerator)
			{
				Coroutine val = ((MonoBehaviour)action).StartCoroutine(enumerator);
				AskRoutineField.Invoke(action) = val;
			}
		}

		private static void StopTrackedCoroutine(Action_AskBingBong action)
		{
			try
			{
				Coroutine val = AskRoutineField.Invoke(action);
				if (val != null)
				{
					((MonoBehaviour)action).StopCoroutine(val);
				}
				AskRoutineField.Invoke(action) = null;
			}
			catch
			{
			}
		}

		private static void StopTrackedSubtitleCoroutine(Action_AskBingBong action)
		{
			try
			{
				Coroutine val = SubtitleRoutineField.Invoke(action);
				if (val != null)
				{
					((MonoBehaviour)action).StopCoroutine(val);
				}
				SubtitleRoutineField.Invoke(action) = null;
			}
			catch
			{
			}
		}

		private static Item? ItemField(Action_AskBingBong action)
		{
			try
			{
				return ItemFieldBase.Invoke((ItemActionBase)(object)action);
			}
			catch
			{
				return null;
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}