using System; 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 EnemyLeash.Patches; using HarmonyLib; using Microsoft.CodeAnalysis; 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: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] [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 EnemyLeash { internal struct ChaseAnchor { public Vector3 Origin; public float StartedAt; } internal static class ChaseTracker { private static readonly Dictionary Anchors = new Dictionary(); internal static ChaseAnchor GetOrCreate(int id, Vector3 currentPosition) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (Anchors.TryGetValue(id, out var value)) { return value; } ChaseAnchor chaseAnchor = new ChaseAnchor { Origin = currentPosition, StartedAt = Time.time }; Anchors[id] = chaseAnchor; return chaseAnchor; } internal static void Forget(int id) { if (Anchors.Count != 0) { Anchors.Remove(id); } } internal static void Clear() { Anchors.Clear(); } } [BepInPlugin("benjamin.enemyleash", "EnemyLeash", "1.0.0")] [BepInProcess("REPO.exe")] public class Plugin : BaseUnityPlugin { public const string Guid = "benjamin.enemyleash"; public const string Name = "EnemyLeash"; public const string Version = "1.0.0"; internal static ManualLogSource Log; private Harmony _harmony; internal static ConfigEntry Enabled; internal static ConfigEntry LeashRadius; internal static ConfigEntry MaxChaseSeconds; internal static ConfigEntry GiveUpCooldown; internal static ConfigEntry BlindOnGiveUp; internal static ConfigEntry Verbose; private void Awake() { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Master switch. Turn off to restore vanilla chase behaviour without uninstalling."); LeashRadius = ((BaseUnityPlugin)this).Config.Bind("Leash", "LeashRadius", 30f, new ConfigDescription("Metres an enemy may travel from where its chase began before it gives up. Set to 0 to disable distance leashing and rely on MaxChaseSeconds alone.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 200f), Array.Empty())); MaxChaseSeconds = ((BaseUnityPlugin)this).Config.Bind("Leash", "MaxChaseSeconds", 0f, new ConfigDescription("Hard cap on how long a single chase can last, in seconds. 0 disables the time limit. Note vanilla enemies already have their own per-type chase timers; this only shortens them.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 300f), Array.Empty())); GiveUpCooldown = ((BaseUnityPlugin)this).Config.Bind("Leash", "GiveUpCooldown", 8f, new ConfigDescription("Seconds an enemy stays unable to start a new chase after being leashed. Stops it re-acquiring you the instant it turns around.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 60f), Array.Empty())); BlindOnGiveUp = ((BaseUnityPlugin)this).Config.Bind("Leash", "BlindOnGiveUp", true, "Also blind the enemy for the cooldown duration, not just block chasing. Without this it will still hear and investigate you."); Verbose = ((BaseUnityPlugin)this).Config.Bind("Debug", "Verbose", false, "Log every leash trigger with the distance and duration that caused it."); _harmony = new Harmony("benjamin.enemyleash"); _harmony.PatchAll(typeof(ChaseLeashPatch)); _harmony.PatchAll(typeof(LevelResetPatch)); Log.LogInfo((object)"EnemyLeash v1.0.0 loaded. Monsters have a rope now."); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } internal static void Trace(string msg) { if (Verbose != null && Verbose.Value) { Log.LogInfo((object)msg); } } } public static class MyPluginInfo { public const string PLUGIN_GUID = "EnemyLeash"; public const string PLUGIN_NAME = "EnemyLeash"; public const string PLUGIN_VERSION = "1.0.0"; } } namespace EnemyLeash.Patches { [HarmonyPatch(typeof(EnemyStateChase))] internal static class ChaseLeashPatch { [HarmonyPostfix] [HarmonyPatch("Update")] private static void AfterUpdate(EnemyStateChase __instance) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.Enabled.Value || !SemiFunc.IsMasterClientOrSingleplayer()) { return; } Enemy enemy = __instance.Enemy; if ((Object)(object)enemy == (Object)null) { return; } int instanceID = ((Object)enemy).GetInstanceID(); if (!__instance.Active) { ChaseTracker.Forget(instanceID); return; } Vector3 val = (((Object)(object)enemy.CenterTransform != (Object)null) ? enemy.CenterTransform.position : ((Component)enemy).transform.position); ChaseAnchor orCreate = ChaseTracker.GetOrCreate(instanceID, val); float num = Vector3.Distance(val, orCreate.Origin); float num2 = Time.time - orCreate.StartedAt; float value = Plugin.LeashRadius.Value; float value2 = Plugin.MaxChaseSeconds.Value; bool flag = value > 0f && num > value; bool flag2 = value2 > 0f && num2 > value2; if (flag || flag2) { GiveUp(enemy, instanceID, num, num2, flag); } } private static void GiveUp(Enemy enemy, int id, float travelled, float elapsed, bool tooFar) { float value = Plugin.GiveUpCooldown.Value; enemy.DisableChase(value); if (Plugin.BlindOnGiveUp.Value && enemy.HasVision && (Object)(object)enemy.Vision != (Object)null) { enemy.Vision.DisableVision(value); } ChaseTracker.Forget(id); string arg = (tooFar ? $"travelled {travelled:F1}m (limit {Plugin.LeashRadius.Value:F1}m)" : $"chased for {elapsed:F1}s (limit {Plugin.MaxChaseSeconds.Value:F1}s)"); string arg2 = (((Object)(object)enemy.EnemyParent != (Object)null) ? enemy.EnemyParent.enemyName : "enemy"); Plugin.Trace($"[EnemyLeash] '{arg2}' gave up - {arg}. Cooldown {value:F1}s."); } } [HarmonyPatch(typeof(RunManager))] internal static class LevelResetPatch { [HarmonyPostfix] [HarmonyPatch("ChangeLevel")] private static void AfterChangeLevel() { ChaseTracker.Clear(); } } }