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)
{
}
}
}