using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
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 GameNetcodeStuff;
using HarmonyLib;
using MaskTheDead.Components;
using Microsoft.CodeAnalysis;
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("MaskTheDead")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("If a dead body is next to a mask there's a chance that the body is possesed")]
[assembly: AssemblyFileVersion("1.0.3.0")]
[assembly: AssemblyInformationalVersion("1.0.3")]
[assembly: AssemblyProduct("MaskTheDead")]
[assembly: AssemblyTitle("MaskTheDead")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.3.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.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace MaskTheDead
{
public class Configuration
{
private ConfigEntry<float> configPossessionChance;
private ConfigEntry<int> configPossessionDelayTime;
private ConfigEntry<int> configRetryPossesionTime;
private ConfigEntry<int> configMaskPossessionRange;
private ConfigFile File;
public float PossesionChance => configPossessionChance.Value;
public int RetryPossesionTime => configRetryPossesionTime.Value;
public int PossessionDelayTime => configPossessionDelayTime.Value;
public int MaskPossessionRange => configMaskPossessionRange.Value;
public int RetryPossesionMinTime => 10000;
public Configuration(ConfigFile config)
{
File = config;
configPossessionChance = File.Bind<float>("General", "PossessionChance", 1f, "A number between 0 and 1 (included) representing the percentage of a body being possessed by a nearby mask");
configRetryPossesionTime = File.Bind<int>("General", "RetryPossesionTime", RetryPossesionMinTime, $"Time in milliseconds after which a mask will attempt to posses a nearby body again, less than {RetryPossesionMinTime} milliseconds is ignored");
configMaskPossessionRange = File.Bind<int>("General", "MaskPossessionRange", 10, "Range, in units, of the mask repossession trigger. Only bodies close enough will be considered for repossessions!");
configPossessionDelayTime = File.Bind<int>("General", "PossessionDelayTime", 0, "Time in milliseconds before the body is actually possessed");
}
}
[BepInPlugin("MaskTheDead", "MaskTheDead", "1.0.3")]
public class Plugin : BaseUnityPlugin
{
public static ManualLogSource TheLogger;
public static Configuration TheConfiguration;
public Plugin()
{
TheConfiguration = new Configuration(((BaseUnityPlugin)this).Config);
}
private void Awake()
{
TheLogger = ((BaseUnityPlugin)this).Logger;
((BaseUnityPlugin)this).Logger.LogInfo((object)"Plugin MaskTheDead is loaded!");
Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null);
}
}
public static class PluginInfo
{
public const string PLUGIN_GUID = "MaskTheDead";
public const string PLUGIN_NAME = "MaskTheDead";
public const string PLUGIN_VERSION = "1.0.3";
}
}
namespace MaskTheDead.Patchers
{
[HarmonyPatch]
public class GrabbableObjectMaskPatcher
{
[HarmonyPrefix]
[HarmonyPatch(typeof(GrabbableObject), "Start")]
private static bool StartPatch(ref GrabbableObject __instance)
{
if ((Object)(object)__instance == (Object)null)
{
Plugin.TheLogger.LogFatal((object)"This instance is null, cannot patch item");
return true;
}
GrabbableObject obj = __instance;
HauntedMaskItem val = (HauntedMaskItem)(object)((obj is HauntedMaskItem) ? obj : null);
if ((Object)(object)val == (Object)null)
{
return true;
}
if (!GameNetworkManager.Instance.isHostingGame)
{
Plugin.TheLogger.LogInfo((object)"Avoding patch for non hosts");
return true;
}
Plugin.TheLogger.LogInfo((object)"Adding mask watcher to haunted mask!");
((Component)val).gameObject.AddComponent<MaskTheDeadComponent>();
return true;
}
}
}
namespace MaskTheDead.Components
{
public class MaskTheDeadComponent : NetworkBehaviour
{
private readonly Dictionary<GameObject, Coroutine> _NearbyBodies = new Dictionary<GameObject, Coroutine>();
private HauntedMaskItem _MaskComponent;
private SphereCollider _Collider;
public bool PossessionChancePassed => Random.value < Plugin.TheConfiguration.PossesionChance;
public bool CanRetryPossession => Plugin.TheConfiguration.RetryPossesionTime >= Plugin.TheConfiguration.RetryPossesionMinTime;
private bool CanComponentUseRagdoll(RagdollGrabbableObject ragdoll)
{
if (((GrabbableObject)ragdoll).isHeld || ((GrabbableObject)ragdoll).isHeldByEnemy)
{
Plugin.TheLogger.LogInfo((object)"Cant attempt possession of held ragdoll...");
return false;
}
if (((GrabbableObject)ragdoll).isInShipRoom)
{
Plugin.TheLogger.LogInfo((object)"Cant attempt possession of body in ship");
return false;
}
return true;
}
private void Awake()
{
_MaskComponent = ((Component)this).gameObject.GetComponent<HauntedMaskItem>();
_Collider = ((Component)this).gameObject.AddComponent<SphereCollider>();
_Collider.radius = Plugin.TheConfiguration.MaskPossessionRange;
((Collider)_Collider).isTrigger = true;
}
private void Update()
{
if (ShouldDispose())
{
Dispose();
return;
}
bool num = ((GrabbableObject)_MaskComponent).isHeldByEnemy || ((GrabbableObject)_MaskComponent).isHeld;
bool isInShipRoom = ((GrabbableObject)_MaskComponent).isInShipRoom;
bool isInElevator = ((GrabbableObject)_MaskComponent).isInElevator;
if (num || isInShipRoom || isInElevator)
{
((Collider)_Collider).enabled = false;
CleanupCoroutines();
}
else
{
((Collider)_Collider).enabled = true;
}
}
public override void OnDestroy()
{
Plugin.TheLogger.LogInfo((object)"Destroying repossession component!");
CleanupCoroutines();
((NetworkBehaviour)this).OnDestroy();
}
public override void OnNetworkDespawn()
{
Plugin.TheLogger.LogInfo((object)"Network despawn, cleaning repossession component!");
((NetworkBehaviour)this).OnNetworkDespawn();
CleanupCoroutines();
}
private void OnTriggerEnter(Collider other)
{
if (ShouldDispose())
{
Dispose();
return;
}
RagdollGrabbableObject colliderRagdoll = GetColliderRagdoll(other);
if ((Object)(object)colliderRagdoll == (Object)null)
{
Plugin.TheLogger.LogInfo((object)"Object in mask range is not ragdoll");
}
else if (CanComponentUseRagdoll(colliderRagdoll) && !_NearbyBodies.ContainsKey(((Component)colliderRagdoll).gameObject))
{
_NearbyBodies.Add(((Component)colliderRagdoll).gameObject, null);
AttemptPossesion(colliderRagdoll);
}
}
private void OnTriggerExit(Collider other)
{
Plugin.TheLogger.LogInfo((object)$"Something is leaving the mask range, i have {_NearbyBodies.Count} body nearby!");
RagdollGrabbableObject colliderRagdoll = GetColliderRagdoll(other);
Coroutine value;
if ((Object)(object)colliderRagdoll == (Object)null)
{
Plugin.TheLogger.LogInfo((object)"The collider leaving is not of a ragdoll");
}
else if (_NearbyBodies.TryGetValue(((Component)colliderRagdoll).gameObject, out value))
{
if (value != null)
{
Plugin.TheLogger.LogInfo((object)"Stopping coroutine for body leaving mask range");
((MonoBehaviour)this).StopCoroutine(value);
}
_NearbyBodies.Remove(((Component)colliderRagdoll).gameObject);
}
else
{
Plugin.TheLogger.LogWarning((object)"Body leaving mask range was not in dicitonary of bodies!");
}
}
private RagdollGrabbableObject GetColliderRagdoll(Collider other)
{
RagdollGrabbableObject component = ((Component)other).gameObject.GetComponent<RagdollGrabbableObject>();
Plugin.TheLogger.LogInfo((object)$"Collided with {((Component)other).gameObject}");
if ((Object)(object)component != (Object)null && component.bodyID.Value >= 0)
{
Plugin.TheLogger.LogInfo((object)"Found ragdoll, lucky!");
return component;
}
if (!((Object)other).name.Contains(".L") && !((Object)other).name.Contains(".R"))
{
Plugin.TheLogger.LogInfo((object)"This is not a bone!");
return null;
}
Plugin.TheLogger.LogInfo((object)"Collided with a bone of a character, finding ragdoll on parents");
return ((Component)other).gameObject.GetComponentInParent<RagdollGrabbableObject>();
}
private IEnumerator RepossessionCoroutine(object o)
{
yield return (object)new WaitForSeconds((float)(Plugin.TheConfiguration.RetryPossesionTime / 1000));
yield return (object)new WaitForEndOfFrame();
RagdollGrabbableObject val = (RagdollGrabbableObject)((o is RagdollGrabbableObject) ? o : null);
if ((Object)(object)val != (Object)null && _NearbyBodies.TryGetValue(((Component)val).gameObject, out var _))
{
Plugin.TheLogger.LogInfo((object)"Ragdoll timer elapsed, attempting possession!");
_NearbyBodies[((Component)val).gameObject] = null;
AttemptPossesion(val);
}
else
{
Plugin.TheLogger.LogWarning((object)"Ragdoll was deleted, stopping repossession retry!");
}
}
private void CleanupCoroutines()
{
if (_NearbyBodies.Count == 0)
{
return;
}
Plugin.TheLogger.LogInfo((object)"Cleaning up all nearby bodies!");
foreach (KeyValuePair<GameObject, Coroutine> nearbyBody in _NearbyBodies)
{
if (nearbyBody.Value != null)
{
((MonoBehaviour)this).StopCoroutine(nearbyBody.Value);
}
}
_NearbyBodies.Clear();
}
private IEnumerator AttemptPossesion(RagdollGrabbableObject ragdoll)
{
_ = ((Component)ragdoll).gameObject;
if (Plugin.TheConfiguration.PossessionDelayTime > 0)
{
yield return (object)new WaitForSeconds((float)(Plugin.TheConfiguration.PossessionDelayTime / 1000));
yield return (object)new WaitForEndOfFrame();
}
if (!_NearbyBodies.ContainsKey(((Component)ragdoll).gameObject))
{
yield break;
}
if (!PossessionChancePassed)
{
Plugin.TheLogger.LogInfo((object)"Chance failed, will not possess body");
if (CanRetryPossession)
{
Plugin.TheLogger.LogInfo((object)"Starting coroutine for repossession");
if (_NearbyBodies[((Component)ragdoll).gameObject] != null)
{
((MonoBehaviour)this).StopCoroutine(_NearbyBodies[((Component)ragdoll).gameObject]);
}
Coroutine value = ((MonoBehaviour)this).StartCoroutine("RepossessionCoroutine", (object)ragdoll);
_NearbyBodies[((Component)ragdoll).gameObject] = value;
}
}
else
{
Plugin.TheLogger.LogInfo((object)"Chance for possession OK");
BeginPossession(ragdoll);
}
}
private void BeginPossession(RagdollGrabbableObject ragdoll)
{
//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
Plugin.TheLogger.LogInfo((object)"!!POSSESSING DEAD BODY!!");
HauntedMaskItem component = ((Component)this).gameObject.GetComponent<HauntedMaskItem>();
if ((Object)(object)component == (Object)null)
{
Plugin.TheLogger.LogFatal((object)"Could not find mask item component, aborting possession!");
return;
}
Plugin.TheLogger.LogInfo((object)"Spawning mimic");
if ((Object)(object)ragdoll.ragdoll.playerScript == (Object)null)
{
Plugin.TheLogger.LogFatal((object)"Ragdoll's player script is null, aborting mimic spawn!");
return;
}
((NetworkBehaviour)component).NetworkObject.RemoveOwnership();
((NetworkBehaviour)ragdoll).NetworkObject.RemoveOwnership();
SetPreviouslyHeldByOf(component, ragdoll.ragdoll.playerScript);
component.CreateMimicServerRpc(((GrabbableObject)ragdoll).isInFactory, ((Component)ragdoll.ragdoll).transform.position);
if ((Object)(object)component != (Object)null)
{
((NetworkBehaviour)component).NetworkObject.Despawn(true);
Object.Destroy((Object)(object)component);
if ((Object)(object)((GrabbableObject)component).radarIcon != (Object)null && (Object)(object)((Component)((GrabbableObject)component).radarIcon).gameObject != (Object)null)
{
Object.Destroy((Object)(object)((Component)((GrabbableObject)component).radarIcon).gameObject);
}
}
if ((Object)(object)ragdoll != (Object)null)
{
((NetworkBehaviour)ragdoll).NetworkObject.Despawn(true);
Object.Destroy((Object)(object)ragdoll);
}
}
private void SetPreviouslyHeldByOf(HauntedMaskItem item, PlayerControllerB c)
{
((object)item).GetType().GetField("previousPlayerHeldBy", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(item, c);
Plugin.TheLogger.LogInfo((object)"Set private field previousPlayerHeldBy");
}
private bool HasFinishedAttaching(HauntedMaskItem item)
{
return (bool)((object)item).GetType().GetField("finishedAttaching", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(item);
}
private bool ShouldDispose()
{
if (HasFinishedAttaching(_MaskComponent))
{
Plugin.TheLogger.LogInfo((object)">> MASK HAS ATTACHED ITSELF TO SOMEONE <<");
Plugin.TheLogger.LogInfo((object)">> KILLING MASKTHEDEAD COMPONENT <<");
return true;
}
if ((Object)(object)_MaskComponent == (Object)null || (Object)(object)_Collider == (Object)null)
{
Plugin.TheLogger.LogWarning((object)">> MASK OR COLLIDER IS NULL <<");
Plugin.TheLogger.LogInfo((object)">> KILLING MASKTHEDEAD COMPONENT <<");
return true;
}
if (StartOfRound.Instance.currentLevel.levelID == 3)
{
return true;
}
return false;
}
private void Dispose()
{
Plugin.TheLogger.LogInfo((object)">> DISPOSING <<");
Object.Destroy((Object)(object)this);
}
}
}