Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of MaskTheDead v1.0.6
BepInEx/plugins/mask-the-dead/MaskTheDead.dll
Decompiled 2 years agousing 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); } } }