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 BetterEnemyAI v1.5.1
BetterEnemyAI.dll
Decompiled a day agousing System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace BetterEnemyAI; [HarmonyPatch(typeof(Terminal), "InitTerminal")] internal static class Commands { private const float DefaultRange = 40f; private static void Postfix() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown new ConsoleCommand("bea_creatures", "[range] - BetterEnemyAI: list creatures near you by the names used in its settings", (ConsoleEvent)delegate(ConsoleEventArgs args) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { args.Context.AddString("Join a world first."); } else { if (args.Length < 2 || !float.TryParse(args[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { result = 40f; } SortedDictionary<string, int> sortedDictionary = new SortedDictionary<string, int>(); Dictionary<string, string> dictionary = new Dictionary<string, string>(); foreach (Character allCharacter in Character.GetAllCharacters()) { if (!((Object)(object)allCharacter == (Object)null) && !allCharacter.IsPlayer() && !(Vector3.Distance(((Component)allCharacter).transform.position, ((Component)localPlayer).transform.position) > result)) { MonsterAI component = ((Component)allCharacter).GetComponent<MonsterAI>(); if (!((Object)(object)component == (Object)null)) { AiState aiState = AiState.Get(component); sortedDictionary.TryGetValue(aiState.PrefabName, out var value); sortedDictionary[aiState.PrefabName] = value + 1; dictionary[aiState.PrefabName] = Describe(allCharacter, aiState); } } } if (sortedDictionary.Count != 0) { args.Context.AddString("Creatures within " + result.ToString("0", CultureInfo.InvariantCulture) + " m:"); { foreach (KeyValuePair<string, int> item in sortedDictionary) { args.Context.AddString(" " + item.Key + " x" + item.Value + " - " + dictionary[item.Key]); } return; } } args.Context.AddString("No creatures within " + result.ToString("0", CultureInfo.InvariantCulture) + " m."); } }, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } private static string Describe(Character character, AiState state) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.Enabled.Value) { return "mod is off"; } if (character.IsTamed() || (int)character.GetFaction() == 0) { return "friendly, not affected"; } if (character.IsBoss() && !Plugin.AffectBosses.Value) { return "boss, not affected"; } if (Plugin.IsExcluded(state.PrefabName)) { return "excluded"; } if (!state.Settings.Enabled) { return "turned off in CreatureOverrides"; } if (state.Settings != CreatureSettings.Defaults) { return "affected, has CreatureOverrides"; } return "affected"; } } internal interface ISynced { string Key { get; } string Serialize(); void ApplyServer(string text); void ClearServer(); } public class Synced<T> : ISynced { public readonly ConfigEntry<T> Entry; private T serverValue; private bool hasServerValue; public T Value { get { if (!hasServerValue) { return Entry.Value; } return serverValue; } } public string Key => ((ConfigEntryBase)Entry).Definition.Section + "/" + ((ConfigEntryBase)Entry).Definition.Key; public event Action Changed; public Synced(ConfigEntry<T> entry) { Entry = entry; EventHandler eventHandler = delegate { RaiseChanged(); ConfigSync.OnLocalChanged(); }; entry.SettingChanged += eventHandler; ConfigSync.Register(this); } public string Serialize() { return TomlTypeConverter.ConvertToString((object)Entry.Value, typeof(T)); } public void ApplyServer(string text) { serverValue = (T)TomlTypeConverter.ConvertToValue(text, typeof(T)); hasServerValue = true; RaiseChanged(); } public void ClearServer() { if (hasServerValue) { hasServerValue = false; RaiseChanged(); } } private void RaiseChanged() { if (this.Changed != null) { this.Changed(); } } } internal static class ConfigSync { [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class NewConnectionPatch { private static void Postfix(ZNet __instance, ZNetPeer __0) { if (__instance.IsServer()) { __0.m_rpc.Register<string>("BetterEnemyAI_Hello", (Action<ZRpc, string>)OnHello); } else { __0.m_rpc.Register<ZPackage>("BetterEnemyAI_Config", (Action<ZRpc, ZPackage>)OnConfig); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private static class PeerInfoPatch { private static void Postfix(ZNet __instance, ZRpc __0) { if (__instance.IsServer()) { ZNetPeer val = FindPeer(__0); if (val != null) { Send(val); ((MonoBehaviour)Plugin.Instance).StartCoroutine(CheckHello(val)); } } } } [HarmonyPatch(typeof(ZNet), "OnDestroy")] private static class LeavePatch { private static void Postfix() { foreach (ISynced setting in settings) { setting.ClearServer(); } peersWithMod.Clear(); } } private const string ConfigRpc = "BetterEnemyAI_Config"; private const string HelloRpc = "BetterEnemyAI_Hello"; private const float HelloTimeout = 20f; private static readonly List<ISynced> settings = new List<ISynced>(); private static readonly HashSet<long> peersWithMod = new HashSet<long>(); private static bool IsServer { get { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } internal static void Register(ISynced setting) { settings.Add(setting); } internal static void OnLocalChanged() { if (!IsServer) { return; } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer.m_rpc != null) { Send(peer); } } } private static void Send(ZNetPeer peer) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write("1.5.1"); val.Write(settings.Count); foreach (ISynced setting in settings) { val.Write(setting.Key); val.Write(setting.Serialize()); } peer.m_rpc.Invoke("BetterEnemyAI_Config", new object[1] { val }); } private static void OnConfig(ZRpc rpc, ZPackage pkg) { string text = pkg.ReadString(); int num = pkg.ReadInt(); Dictionary<string, string> dictionary = new Dictionary<string, string>(); for (int i = 0; i < num; i++) { string key = pkg.ReadString(); dictionary[key] = pkg.ReadString(); } foreach (ISynced setting in settings) { if (dictionary.TryGetValue(setting.Key, out var value)) { try { setting.ApplyServer(value); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not apply server value for " + setting.Key + ": " + ex.Message)); } } } Plugin.Log.LogInfo((object)"Using the server's BetterEnemyAI settings."); if (text != "1.5.1") { Warn("BetterEnemyAI version mismatch: server has " + text + ", you have 1.5.1."); } rpc.Invoke("BetterEnemyAI_Hello", new object[1] { "1.5.1" }); } private static void OnHello(ZRpc rpc, string clientVersion) { ZNetPeer val = FindPeer(rpc); if (val != null) { peersWithMod.Add(val.m_uid); if (clientVersion != "1.5.1") { Warn(val.m_playerName + " has BetterEnemyAI " + clientVersion + ", the server has 1.5.1."); } } } private static IEnumerator CheckHello(ZNetPeer peer) { yield return (object)new WaitForSeconds(20f); if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.GetPeers().Contains(peer) && !peersWithMod.Contains(peer.m_uid)) { Warn(peer.m_playerName + " doesn't have BetterEnemyAI installed. Enemies near them will behave like vanilla."); } } private static ZNetPeer FindPeer(ZRpc rpc) { foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer.m_rpc == rpc) { return peer; } } return null; } private static void Warn(string message) { Plugin.Log.LogWarning((object)message); if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)1, message, 0, (Sprite)null, false, true); } } } internal class CreatureSettings { internal static readonly CreatureSettings Defaults = new CreatureSettings(); public bool? enabled; public bool? noCircleAfterAttack; public float? groundFrontArc; public FlyingMode? flyingAfterAttack; public float? retreatDistance; public float? flyingWander; public float? flyingFrontArc; public bool? noPeriodicCircling; public float? giveUpWhenNotSensed; public float? giveUpWhenNoCombat; public float? maxChaseDistance; public float? giveUpWhenUnreachable; public float? ignoreAfterGiveUp; public bool? returnAfterGiveUp; public bool Enabled => enabled ?? true; public bool NoCircleAfterAttack => noCircleAfterAttack ?? Plugin.NoCircleAfterAttack.Value; public float GroundFrontArc => groundFrontArc ?? Plugin.GroundFrontArc.Value; public FlyingMode FlyingAfterAttack => flyingAfterAttack ?? Plugin.FlyingAfterAttack.Value; public float RetreatDistance => retreatDistance ?? Plugin.RetreatDistance.Value; public float FlyingWander => flyingWander ?? Plugin.FlyingWander.Value; public float FlyingFrontArc => flyingFrontArc ?? Plugin.FlyingFrontArc.Value; public bool NoPeriodicCircling => noPeriodicCircling ?? Plugin.NoPeriodicCircling.Value; public float GiveUpWhenNotSensed => giveUpWhenNotSensed ?? Plugin.GiveUpWhenNotSensed.Value; public float GiveUpWhenNoCombat => giveUpWhenNoCombat ?? Plugin.GiveUpWhenNoCombat.Value; public float MaxChaseDistance => maxChaseDistance ?? Plugin.MaxChaseDistance.Value; public float GiveUpWhenUnreachable => giveUpWhenUnreachable ?? Plugin.GiveUpWhenUnreachable.Value; public float IgnoreAfterGiveUp => ignoreAfterGiveUp ?? Plugin.IgnoreAfterGiveUp.Value; public bool ReturnAfterGiveUp => returnAfterGiveUp ?? Plugin.ReturnAfterGiveUp.Value; } internal static class CreatureOverrides { private static Dictionary<string, CreatureSettings> byName = new Dictionary<string, CreatureSettings>(StringComparer.OrdinalIgnoreCase); internal static int Version; internal static CreatureSettings Find(string prefabName) { if (!byName.TryGetValue(prefabName, out var value)) { return CreatureSettings.Defaults; } return value; } internal static void Parse(string text) { Dictionary<string, CreatureSettings> dictionary = new Dictionary<string, CreatureSettings>(StringComparer.OrdinalIgnoreCase); string[] array = text.Split(';'); foreach (string text2 in array) { string text3 = text2.Trim(); if (text3.Length == 0) { continue; } int num = text3.IndexOf(':'); if (num <= 0) { Warn(text3, "expected 'CreatureName: Setting=Value'"); continue; } string text4 = text3.Substring(0, num).Trim(); if (!dictionary.TryGetValue(text4, out var value)) { value = (dictionary[text4] = new CreatureSettings()); } string[] array2 = text3.Substring(num + 1).Split(','); foreach (string text5 in array2) { string text6 = text5.Trim(); if (text6.Length != 0) { int num2 = text6.IndexOf('='); string text7 = ((num2 <= 0) ? "expected Setting=Value" : Apply(value, text6.Substring(0, num2).Trim().ToLowerInvariant(), text6.Substring(num2 + 1).Trim())); if (text7 != null) { Warn(text4 + ": " + text6, text7); } } } } byName = dictionary; Version++; } private static string Apply(CreatureSettings s, string key, string value) { switch (key) { case "enabled": return ParseBool(value, out s.enabled); case "nocircleafterattack": return ParseBool(value, out s.noCircleAfterAttack); case "noperiodiccircling": return ParseBool(value, out s.noPeriodicCircling); case "groundfrontarc": return ParseFloat(value, out s.groundFrontArc); case "flyingafterattack": return ParseFlying(value, out s.flyingAfterAttack); case "retreatdistance": return ParseFloat(value, out s.retreatDistance); case "flyingwander": return ParseFloat(value, out s.flyingWander); case "flyingfrontarc": return ParseFloat(value, out s.flyingFrontArc); case "giveupwhennotsensedseconds": return ParseFloat(value, out s.giveUpWhenNotSensed); case "giveupwhennocombatseconds": return ParseFloat(value, out s.giveUpWhenNoCombat); case "maxchasedistance": return ParseFloat(value, out s.maxChaseDistance); case "giveupwhenunreachableseconds": return ParseFloat(value, out s.giveUpWhenUnreachable); case "ignoreaftergiveupseconds": return ParseFloat(value, out s.ignoreAfterGiveUp); case "returnaftergiveup": return ParseBool(value, out s.returnAfterGiveUp); case "chase": if (!IsVanilla(value)) { return "only Chase=Vanilla is supported"; } s.giveUpWhenNotSensed = 0f; s.giveUpWhenNoCombat = 0f; s.maxChaseDistance = 0f; s.giveUpWhenUnreachable = 0f; return null; case "circling": if (!IsVanilla(value)) { return "only Circling=Vanilla is supported"; } s.noCircleAfterAttack = false; s.noPeriodicCircling = false; s.flyingAfterAttack = FlyingMode.Vanilla; return null; default: return "unknown setting '" + key + "'"; } } private static bool IsVanilla(string value) { return string.Equals(value, "vanilla", StringComparison.OrdinalIgnoreCase); } private static string ParseBool(string value, out bool? result) { result = null; switch (value.ToLowerInvariant()) { case "true": case "on": case "yes": case "1": result = true; return null; case "false": case "off": case "no": case "0": result = false; return null; default: return "'" + value + "' is not true/false"; } } private static string ParseFloat(string value, out float? result) { result = null; if (!float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2)) { return "'" + value + "' is not a number (use a dot for decimals, e.g. 1.5)"; } result = result2; return null; } private static string ParseFlying(string value, out FlyingMode? result) { result = null; foreach (FlyingMode value2 in Enum.GetValues(typeof(FlyingMode))) { if (string.Equals(value2.ToString(), value, StringComparison.OrdinalIgnoreCase)) { result = value2; return null; } } return "'" + value + "' is not Vanilla, HitAndRetreat or Hover"; } private static void Warn(string entry, string problem) { Plugin.Log.LogWarning((object)("CreatureOverrides: skipped '" + entry + "': " + problem)); } } public enum FlyingMode { Vanilla, HitAndRetreat, Hover } [BepInPlugin("BetterEnemyAI", "BetterEnemyAI", "1.5.1")] public class Plugin : BaseUnityPlugin { public const string Guid = "BetterEnemyAI"; public const string ModName = "BetterEnemyAI"; public const string Version = "1.5.1"; internal static Synced<bool> Enabled; internal static Synced<string> ExcludedCreatures; internal static Synced<bool> AffectBosses; internal static Synced<bool> AffectRaidMonsters; internal static Synced<bool> TamingDoesntBlockSleep; internal static Synced<bool> NoCircleAfterAttack; internal static Synced<float> GroundFrontArc; internal static Synced<FlyingMode> FlyingAfterAttack; internal static Synced<float> RetreatDistance; internal static Synced<float> FlyingWander; internal static Synced<float> FlyingFrontArc; internal static Synced<bool> NoPeriodicCircling; internal static Synced<float> GiveUpWhenNotSensed; internal static Synced<float> GiveUpWhenNoCombat; internal static Synced<float> MaxChaseDistance; internal static Synced<float> GiveUpWhenUnreachable; internal static Synced<float> IgnoreAfterGiveUp; internal static Synced<bool> ReturnAfterGiveUp; internal static Synced<string> CreatureOverridesText; internal static ConfigEntry<bool> DebugLog; internal static Plugin Instance; internal static ManualLogSource Log; private static HashSet<string> excluded = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private Synced<T> Bind<T>(string section, string key, T defaultValue, ConfigDescription description) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown return new Synced<T>(((BaseUnityPlugin)this).Config.Bind<T>(section, key, defaultValue, new ConfigDescription(description.Description + " In multiplayer, the server's value is used.", description.AcceptableValues, new object[0]))); } private Synced<T> Bind<T>(string section, string key, T defaultValue, string description) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown return Bind(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)null, new object[0])); } private void Awake() { //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Expected O, but got Unknown //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Expected O, but got Unknown //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Expected O, but got Unknown //IL_0302: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; Enabled = Bind("1 - General", "Enabled", defaultValue: true, "Turns the mod on or off."); ExcludedCreatures = Bind("1 - General", "ExcludedCreatures", "", "Prefab names of creatures the mod should not affect, separated by commas. Example: Wolf,Deathsquito,Troll"); AffectBosses = Bind("1 - General", "AffectBosses", defaultValue: false, "Should bosses be affected too?"); AffectRaidMonsters = Bind("1 - General", "AffectRaidMonsters", defaultValue: false, "Also shorten chases by creatures hunting the player during a raid? (Circling settings always apply.)"); TamingDoesntBlockSleep = Bind("1 - General", "TamingDoesntBlockSleep", defaultValue: true, "An animal you're taming (fed, taming started) no longer counts as an enemy nearby when you try to sleep. Wild creatures still do."); NoCircleAfterAttack = Bind("2 - Circling", "NoCircleAfterAttack", defaultValue: true, "Ground enemies stop circling around you after attacking while their next attack recharges; they stand still and face you instead."); GroundFrontArc = Bind("2 - Circling", "GroundFrontArc", 90f, new ConfigDescription("With NoCircleAfterAttack, a ground enemy that ends up beside or behind you after attacking (e.g. a boar charging past) walks back around to your front before waiting, if it is more than this many degrees from where you face. 90 = it waits only in front of you, 180 = it waits wherever it is.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(30f, 180f), new object[0])); FlyingAfterAttack = Bind("2 - Circling", "FlyingAfterAttack", FlyingMode.HitAndRetreat, "What flying enemies (deathsquitos, bats, drakes, etc.) do while their next attack recharges. Vanilla = circle around you as usual. HitAndRetreat = back off a short distance, hover facing you, then dive when ready. Hover = stay where they are and face you."); RetreatDistance = Bind("2 - Circling", "RetreatDistance", 6f, "How many meters flying enemies back off from you in HitAndRetreat mode."); FlyingWander = Bind("2 - Circling", "FlyingWander", 1f, new ConfigDescription("How much flying enemies move around you while waiting in HitAndRetreat mode. 0 = hover in place, 1 = slow, loose circling (default), 2 = faster and wider.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), new object[0])); FlyingFrontArc = Bind("2 - Circling", "FlyingFrontArc", 90f, new ConfigDescription("In HitAndRetreat mode, how far (in degrees) flying enemies may circle to either side of where you are facing. 90 = they stay in front of you or at your sides, 180 = anywhere, including behind you (like vanilla).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(30f, 180f), new object[0])); NoPeriodicCircling = Bind("2 - Circling", "NoPeriodicCircling", defaultValue: true, "Stops some enemies from periodically breaking off their attack to circle around their target."); GiveUpWhenNotSensed = Bind("3 - Chase", "GiveUpWhenNotSensedSeconds", 10f, "An enemy gives up the chase if it can't see or hear you for this many seconds. Vanilla is 30. 0 = vanilla behavior."); GiveUpWhenNoCombat = Bind("3 - Chase", "GiveUpWhenNoCombatSeconds", 20f, "An enemy gives up the chase if for this many seconds it neither hits you nor takes damage from you (even if it can see you). Vanilla is 60. 0 = off."); MaxChaseDistance = Bind("3 - Chase", "MaxChaseDistance", 60f, "An enemy gives up the chase once it is this many meters away from where the chase started (unless you're fighting: it attacked or got hit in the last 5 seconds). 0 = off."); GiveUpWhenUnreachable = Bind("3 - Chase", "GiveUpWhenUnreachableSeconds", 6f, "A ground enemy gives up the chase if it can't find a path to you (e.g. you're on a rock) for this many seconds, unless you're fighting (it attacked or got hit in the last 5 seconds). Near your buildings, enemies that attack buildings keep vanilla behavior. 0 = off."); IgnoreAfterGiveUp = Bind("3 - Chase", "IgnoreAfterGiveUpSeconds", 8f, "An enemy that gave up won't look for a new target for this many seconds. If you hit it, it fights back right away."); ReturnAfterGiveUp = Bind("3 - Chase", "ReturnAfterGiveUp", defaultValue: true, "An enemy that gave up walks straight back to where the chase started, instead of slowly wandering around where it stopped."); CreatureOverridesText = Bind("4 - Per creature", "CreatureOverrides", "", "Different settings for specific creatures. Format: 'Name: Setting=Value, Setting=Value; Name: ...'. Use the setting names above, e.g. 'Troll: GiveUpWhenNoCombatSeconds=30, MaxChaseDistance=80; Greydwarf: GiveUpWhenNoCombatSeconds=15'. Shortcuts: Enabled=false (mod off for that creature), Chase=Vanilla (normal chasing), Circling=Vanilla (normal circling), e.g. 'Deathsquito: Chase=Vanilla'. Use a dot for decimals. To see creature names, open the console (F5) near them and type: bea_creatures"); DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "DebugLog", false, "Writes what enemies decide (e.g. why one gave up a chase) to the BepInEx log. Useful when reporting a problem. Not synced in multiplayer."); ExcludedCreatures.Changed += ParseExcluded; ParseExcluded(); CreatureOverridesText.Changed += delegate { CreatureOverrides.Parse(CreatureOverridesText.Value); }; CreatureOverrides.Parse(CreatureOverridesText.Value); new Harmony("BetterEnemyAI").PatchAll(typeof(Plugin).Assembly); } private static void ParseExcluded() { excluded.Clear(); string[] array = ExcludedCreatures.Value.Split(','); foreach (string text in array) { string text2 = text.Trim(); if (text2.Length > 0) { excluded.Add(text2); } } } internal static bool Affects(Character character, AiState state) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (!Enabled.Value || (Object)(object)character == (Object)null) { return false; } if (character.IsTamed() || (int)character.GetFaction() == 0) { return false; } if (character.IsBoss() && !AffectBosses.Value) { return false; } if (!excluded.Contains(state.PrefabName)) { return state.Settings.Enabled; } return false; } internal static void Debug(string message) { if (DebugLog != null && DebugLog.Value) { Log.LogInfo((object)message); } } internal static bool IsExcluded(string prefabName) { return excluded.Contains(prefabName); } } internal class AiState { private static readonly ConditionalWeakTable<MonsterAI, AiState> table = new ConditionalWeakTable<MonsterAI, AiState>(); public string PrefabName; private CreatureSettings settings; private int settingsVersion = -1; public bool Modified; public bool Circulate; public bool CirculateFlying; public float CircleInterval; public Character ChaseTarget; public Vector3 ChaseStart; public float UnreachableSince = -1f; public float NextPathCheck; public bool Repositioning; public bool RunUpPending; private bool? isCharger; public bool Returning; public Vector3 ReturnTarget; public float ReturnBestDistance; public float ReturnProgressTime; public float NoiseSeed = Random.Range(0f, 1000f); public float LastRetreatTime = -1f; public Vector3 OrbitCenter; public float OrbitAngle; public float OrbitRadius; public Vector3 Move; public bool Running; public CreatureSettings Settings { get { if (settingsVersion != CreatureOverrides.Version) { settings = CreatureOverrides.Find(PrefabName); settingsVersion = CreatureOverrides.Version; } return settings; } } public bool IsCharger(Character character) { if (!isCharger.HasValue) { Humanoid val = (Humanoid)(object)((character is Humanoid) ? character : null); isCharger = (Object)(object)val != (Object)null && RunUpPatch.ShortestMinRange(val) > 0f; } return isCharger.Value; } public static AiState Get(MonsterAI ai) { if (!table.TryGetValue(ai, out var value)) { value = new AiState(); string name = ((Object)((Component)ai).gameObject).name; int num = name.IndexOf("(Clone)", StringComparison.Ordinal); value.PrefabName = ((num >= 0) ? name.Substring(0, num).Trim() : name); table.Add(ai, value); } return value; } } [HarmonyPatch(typeof(MonsterAI), "UpdateAI")] internal static class CirclingPatch { internal static MonsterAI Retreater; internal static MonsterAI Repositioner; private static void Prefix(MonsterAI __instance, Character ___m_character) { Retreater = null; Repositioner = null; AiState aiState = AiState.Get(__instance); if (!Plugin.Affects(___m_character, aiState)) { if (aiState.Modified) { __instance.m_circulateWhileCharging = aiState.Circulate; __instance.m_circulateWhileChargingFlying = aiState.CirculateFlying; __instance.m_circleTargetInterval = aiState.CircleInterval; aiState.Modified = false; } return; } if (!aiState.Modified) { aiState.Circulate = __instance.m_circulateWhileCharging; aiState.CirculateFlying = __instance.m_circulateWhileChargingFlying; aiState.CircleInterval = __instance.m_circleTargetInterval; aiState.Modified = true; } CreatureSettings settings = aiState.Settings; bool avoidLand = __instance.m_avoidLand; bool flag = ___m_character.IsSwimming(); bool flag2 = settings.NoCircleAfterAttack && settings.GroundFrontArc < 180f; __instance.m_circulateWhileCharging = aiState.Circulate && (avoidLand || !settings.NoCircleAfterAttack || flag2); FlyingMode flyingAfterAttack = settings.FlyingAfterAttack; __instance.m_circulateWhileChargingFlying = aiState.CirculateFlying && flyingAfterAttack != FlyingMode.Hover; __instance.m_circleTargetInterval = ((settings.NoPeriodicCircling && !avoidLand) ? 0f : aiState.CircleInterval); if (___m_character.IsFlying()) { if (flyingAfterAttack == FlyingMode.HitAndRetreat && aiState.CirculateFlying) { Retreater = __instance; } } else if (flag2 && aiState.Circulate && !avoidLand && !flag) { Repositioner = __instance; } } private static void Postfix() { Retreater = null; Repositioner = null; } } [HarmonyPatch(typeof(BaseAI), "RandomMovementArroundPoint")] internal static class RetreatPatch { private const float NewRetreatAfter = 0.5f; private const float OrbitSpeed = 30f; private const float CenterLag = 1.5f; private const float SteerSmoothing = 0.45f; private const float HoverHeight = 2.5f; private const float RadiusWobble = 1.5f; private const float HeightWobble = 0.8f; private static readonly Action<BaseAI, Vector3> lookTowards = AccessTools.MethodDelegate<Action<BaseAI, Vector3>>(AccessTools.Method(typeof(BaseAI), "LookTowards", new Type[1] { typeof(Vector3) }, (Type[])null), (object)null, true); private static readonly Action<BaseAI> chargeStop = AccessTools.MethodDelegate<Action<BaseAI>>(AccessTools.Method(typeof(BaseAI), "ChargeStop", (Type[])null, (Type[])null), (object)null, true); private static bool Prefix(BaseAI __instance, float __0, Vector3 __1, Character ___m_character) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Unknown result type (might be due to invalid IL or missing references) //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_02e4: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Unknown result type (might be due to invalid IL or missing references) //IL_02eb: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_02f4: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_030f: Unknown result type (might be due to invalid IL or missing references) //IL_0313: Unknown result type (might be due to invalid IL or missing references) //IL_0325: Unknown result type (might be due to invalid IL or missing references) //IL_0308: Unknown result type (might be due to invalid IL or missing references) //IL_032a: Unknown result type (might be due to invalid IL or missing references) //IL_032e: Unknown result type (might be due to invalid IL or missing references) //IL_0333: Unknown result type (might be due to invalid IL or missing references) //IL_0349: Unknown result type (might be due to invalid IL or missing references) //IL_034e: Unknown result type (might be due to invalid IL or missing references) //IL_0386: Unknown result type (might be due to invalid IL or missing references) //IL_038b: Unknown result type (might be due to invalid IL or missing references) //IL_038d: Unknown result type (might be due to invalid IL or missing references) //IL_0392: Unknown result type (might be due to invalid IL or missing references) //IL_03c2: Unknown result type (might be due to invalid IL or missing references) //IL_03c4: Unknown result type (might be due to invalid IL or missing references) //IL_041a: Unknown result type (might be due to invalid IL or missing references) //IL_042f: Unknown result type (might be due to invalid IL or missing references) //IL_03e4: Unknown result type (might be due to invalid IL or missing references) //IL_03eb: Unknown result type (might be due to invalid IL or missing references) //IL_0402: Unknown result type (might be due to invalid IL or missing references) //IL_0407: Unknown result type (might be due to invalid IL or missing references) MonsterAI retreater = CirclingPatch.Retreater; if ((Object)(object)retreater == (Object)null || (Object)(object)retreater != (Object)(object)__instance) { return true; } Character targetCreature = ((BaseAI)retreater).GetTargetCreature(); if (!((Object)(object)targetCreature == (Object)null)) { Vector3 val = ((Component)targetCreature).transform.position - __1; if (!(((Vector3)(ref val)).sqrMagnitude > 0.01f)) { AiState aiState = AiState.Get(retreater); CreatureSettings settings = aiState.Settings; float num = Mathf.Max(settings.FlyingWander, 0f); Vector3 position = ((Component)retreater).transform.position; if (Time.time - aiState.LastRetreatTime > 0.5f) { Vector3 val2 = position - __1; val2.y = 0f; if (((Vector3)(ref val2)).sqrMagnitude < 0.01f) { val2 = -((Component)retreater).transform.forward; } aiState.OrbitCenter = __1; aiState.OrbitAngle = Mathf.Atan2(val2.x, val2.z) * 57.29578f; aiState.OrbitRadius = Mathf.Max(settings.RetreatDistance, 1f) * Random.Range(0.9f, 1.2f); aiState.Move = Vector3.zero; aiState.Running = true; } aiState.LastRetreatTime = Time.time; aiState.OrbitCenter = Vector3.Lerp(aiState.OrbitCenter, __1, 1f - Mathf.Exp((0f - __0) / 1.5f)); float time = Time.time; float noiseSeed = aiState.NoiseSeed; float num2 = (Mathf.PerlinNoise(noiseSeed, time * 0.25f) - 0.5f) * 2.5f; aiState.OrbitAngle += num2 * 30f * num * __0; float num3 = Mathf.Clamp(settings.FlyingFrontArc, 0f, 180f); if (num3 < 180f) { Vector3 forward = ((Component)targetCreature).transform.forward; float num4 = Mathf.Atan2(forward.x, forward.z) * 57.29578f; float num5 = Mathf.DeltaAngle(num4, aiState.OrbitAngle); if (Mathf.Abs(num5) > num3) { aiState.OrbitAngle = Mathf.MoveTowardsAngle(aiState.OrbitAngle, num4 + Mathf.Sign(num5) * num3, 60f * __0); } } float num6 = aiState.OrbitRadius + (Mathf.PerlinNoise(noiseSeed + 13.1f, time * 0.3f) - 0.5f) * 2f * 1.5f * num; float num7 = aiState.OrbitAngle * ((float)Math.PI / 180f); Vector3 point = aiState.OrbitCenter + new Vector3(Mathf.Sin(num7), 0f, Mathf.Cos(num7)) * num6; point.y = aiState.OrbitCenter.y + 2.5f + (Mathf.PerlinNoise(noiseSeed + 47.9f, time * 0.35f) - 0.5f) * 2f * 0.8f * Mathf.Min(num, 1f); point = KeepAboveGround(retreater, point); Vector3 val3 = point - position; float magnitude = ((Vector3)(ref val3)).magnitude; Vector3 val4 = ((magnitude > 0.1f) ? (val3 / magnitude * Mathf.Clamp01(magnitude / 2f)) : Vector3.zero); aiState.Move = Vector3.Lerp(aiState.Move, val4, 1f - Mathf.Exp((0f - __0) / 0.45f)); if (aiState.Running && magnitude < 3f) { aiState.Running = false; } else if (!aiState.Running && magnitude > 6f) { aiState.Running = true; } Vector3 val5 = targetCreature.GetTopPoint() - position; val5.y = 0f; Vector3 val6 = default(Vector3); ((Vector3)(ref val6))..ctor(aiState.Move.x, 0f, aiState.Move.z); Vector3 arg = val5; if (((Vector3)(ref val6)).sqrMagnitude > 0.0001f && ((Vector3)(ref val5)).sqrMagnitude > 0.0001f) { arg = Vector3.Slerp(((Vector3)(ref val5)).normalized, ((Vector3)(ref val6)).normalized, Mathf.Clamp01(((Vector3)(ref val6)).magnitude * 1.5f)); } chargeStop((BaseAI)(object)retreater); lookTowards((BaseAI)(object)retreater, arg); ___m_character.SetRun(aiState.Running); ___m_character.SetMoveDir(aiState.Move); return false; } } return true; } private static Vector3 KeepAboveGround(MonsterAI ai, Vector3 point) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) float num = ZoneSystem.instance.GetSolidHeight(point) + ((BaseAI)ai).m_flyAltitudeMin + 0.5f; if (point.y < num) { point.y = num; } return point; } } [HarmonyPatch(typeof(BaseAI), "RandomMovementArroundPoint")] internal static class RepositionPatch { private const float Gap = 1.5f; private const float BackInFront = 0.7f; private const float MinRangeMargin = 1f; private const float TooCloseMargin = 0.75f; private static readonly Func<BaseAI, float, Vector3, float, bool, bool> moveTo = AccessTools.MethodDelegate<Func<BaseAI, float, Vector3, float, bool, bool>>(AccessTools.Method(typeof(BaseAI), "MoveTo", new Type[4] { typeof(float), typeof(Vector3), typeof(float), typeof(bool) }, (Type[])null), (object)null, true); private static readonly Action<BaseAI, Vector3> lookAt = AccessTools.MethodDelegate<Action<BaseAI, Vector3>>(AccessTools.Method(typeof(BaseAI), "LookAt", new Type[1] { typeof(Vector3) }, (Type[])null), (object)null, true); private static readonly Action<BaseAI> stopMoving = AccessTools.MethodDelegate<Action<BaseAI>>(AccessTools.Method(typeof(BaseAI), "StopMoving", (Type[])null, (Type[])null), (object)null, true); private static readonly Action<BaseAI> chargeStop = AccessTools.MethodDelegate<Action<BaseAI>>(AccessTools.Method(typeof(BaseAI), "ChargeStop", (Type[])null, (Type[])null), (object)null, true); private static bool Prefix(BaseAI __instance, float __0, Vector3 __1, Character ___m_character) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) MonsterAI repositioner = CirclingPatch.Repositioner; if ((Object)(object)repositioner == (Object)null || (Object)(object)repositioner != (Object)(object)__instance) { return true; } Character targetCreature = ((BaseAI)repositioner).GetTargetCreature(); if (!((Object)(object)targetCreature == (Object)null)) { Vector3 val = ((Component)targetCreature).transform.position - __1; if (!(((Vector3)(ref val)).sqrMagnitude > 0.01f)) { AiState aiState = AiState.Get(repositioner); float num = Mathf.Clamp(aiState.Settings.GroundFrontArc, 0f, 180f); Vector3 position = ((Component)repositioner).transform.position; Vector3 val2 = position - __1; val2.y = 0f; Vector3 forward = ((Component)targetCreature).transform.forward; float num2 = Mathf.Atan2(forward.x, forward.z) * 57.29578f; float num3 = Mathf.Atan2(val2.x, val2.z) * 57.29578f; float num4 = Mathf.DeltaAngle(num2, num3); float num5 = StandDistance(___m_character, targetCreature); if (aiState.IsCharger(___m_character)) { aiState.Repositioning = false; } else if (Mathf.Abs(num4) > num) { aiState.Repositioning = true; } else if (Mathf.Abs(num4) < num * 0.7f) { aiState.Repositioning = false; } chargeStop((BaseAI)(object)repositioner); if (aiState.Repositioning) { float num6 = ((num4 < 0f) ? (-1f) : 1f); float num7 = (num2 + num6 * num * 0.5f) * ((float)Math.PI / 180f); Vector3 arg = __1 + new Vector3(Mathf.Sin(num7), 0f, Mathf.Cos(num7)) * (num5 + 0.5f); moveTo((BaseAI)(object)repositioner, __0, arg, 0.5f, arg5: true); return false; } float magnitude = ((Vector3)(ref val2)).magnitude; if (magnitude > num5 + 0.5f) { moveTo((BaseAI)(object)repositioner, __0, __1, num5, arg5: true); } else if (magnitude < num5 - 0.75f) { Vector3 val3 = ((magnitude > 0.01f) ? (val2 / magnitude) : (-((Component)repositioner).transform.forward)); moveTo((BaseAI)(object)repositioner, __0, __1 + val3 * num5, 0.5f, arg5: true); } else { stopMoving((BaseAI)(object)repositioner); lookAt((BaseAI)(object)repositioner, targetCreature.GetTopPoint()); } return false; } } return true; } private static float StandDistance(Character character, Character target) { float num = character.GetRadius() + target.GetRadius() + 1.5f; Humanoid val = (Humanoid)(object)((character is Humanoid) ? character : null); ItemData val2 = (((Object)(object)val != (Object)null) ? val.GetCurrentWeapon() : null); if (val2 == null) { return num; } float aiAttackRangeMin = val2.m_shared.m_aiAttackRangeMin; if (aiAttackRangeMin <= 0f) { return num; } num = Mathf.Max(num, aiAttackRangeMin + 1f); float num2 = val2.m_shared.m_aiAttackRange + target.GetRadius(); if (num2 > aiAttackRangeMin + 1f) { num = Mathf.Min(num, num2 - 0.3f); } return num; } } [HarmonyPatch(typeof(BaseAI), "IdleMovement")] internal static class RunUpPatch { private const float Margin = 1f; private static readonly FieldInfo updateWeaponTimer = AccessTools.Field(typeof(MonsterAI), "m_updateWeaponTimer"); private static readonly Func<BaseAI, float, Vector3, float, bool, bool> moveTo = AccessTools.MethodDelegate<Func<BaseAI, float, Vector3, float, bool, bool>>(AccessTools.Method(typeof(BaseAI), "MoveTo", new Type[4] { typeof(float), typeof(Vector3), typeof(float), typeof(bool) }, (Type[])null), (object)null, true); private static bool Prefix(BaseAI __instance, float __0, Character ___m_character) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) MonsterAI val = (MonsterAI)(object)((__instance is MonsterAI) ? __instance : null); if ((Object)(object)val == (Object)null) { return true; } Humanoid val2 = (Humanoid)(object)((___m_character is Humanoid) ? ___m_character : null); if ((Object)(object)val2 == (Object)null || val2.GetCurrentWeapon() != null || ___m_character.IsFlying() || ___m_character.IsSwimming() || val.m_avoidLand) { return true; } Character targetCreature = ((BaseAI)val).GetTargetCreature(); if ((Object)(object)targetCreature == (Object)null) { return true; } AiState aiState = AiState.Get(val); if (aiState.Returning || !Plugin.Affects(___m_character, aiState) || !aiState.Settings.NoCircleAfterAttack) { return true; } float num = ShortestMinRange(val2); if (num <= 0f) { return true; } Vector3 val3 = ((Component)val).transform.position - ((Component)targetCreature).transform.position; val3.y = 0f; float magnitude = ((Vector3)(ref val3)).magnitude; if (magnitude >= num) { if (aiState.RunUpPending) { updateWeaponTimer.SetValue(val, 0f); aiState.RunUpPending = false; } return true; } Vector3 val4 = ((magnitude > 0.01f) ? (val3 / magnitude) : (-((Component)val).transform.forward)); moveTo((BaseAI)(object)val, __0, ((Component)targetCreature).transform.position + val4 * (num + 1f), 0.5f, arg5: true); if (!aiState.RunUpPending) { Plugin.Debug(aiState.PrefabName + " backs off " + num + " m for a run-up"); } aiState.RunUpPending = true; return false; } internal static float ShortestMinRange(Humanoid humanoid) { float num = float.MaxValue; foreach (ItemData allItem in humanoid.GetInventory().GetAllItems()) { if (allItem.IsWeapon()) { num = Mathf.Min(num, allItem.m_shared.m_aiAttackRangeMin); } } if (num != float.MaxValue) { return Mathf.Max(num, 0f); } return 0f; } } [HarmonyPatch(typeof(MonsterAI), "UpdateTarget")] internal static class ChasePatch { private const float CombatGrace = 5f; private const float PathCheckInterval = 1f; private const float BaseRadius = 12f; private static readonly MethodInfo setAlerted = AccessTools.Method(typeof(BaseAI), "SetAlerted", (Type[])null, (Type[])null); private static readonly Func<BaseAI, Vector3, bool> havePath = AccessTools.MethodDelegate<Func<BaseAI, Vector3, bool>>(AccessTools.Method(typeof(BaseAI), "HavePath", new Type[1] { typeof(Vector3) }, (Type[])null), (object)null, true); private static readonly List<Piece> nearbyPieces = new List<Piece>(); private static bool IsUnreachable(MonsterAI ai, Character character, AiState state, Character target, float seconds) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) if (seconds <= 0f || character.IsFlying()) { return false; } if (Time.time >= state.NextPathCheck) { state.NextPathCheck = Time.time + 1f; if (havePath((BaseAI)(object)ai, ((Component)target).transform.position)) { state.UnreachableSince = -1f; } else if (state.UnreachableSince < 0f) { state.UnreachableSince = Time.time; } } if (state.UnreachableSince < 0f || Time.time - state.UnreachableSince <= seconds) { return false; } if (ai.m_attackPlayerObjects) { return !NearPlayerBuildings(((Component)target).transform.position); } return true; } private static bool NearPlayerBuildings(Vector3 position) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) nearbyPieces.Clear(); Piece.GetAllPiecesInRadius(position, 12f, nearbyPieces); foreach (Piece nearbyPiece in nearbyPieces) { if ((Object)(object)nearbyPiece != (Object)null && nearbyPiece.IsPlacedByPlayer()) { return true; } } return false; } private static void Postfix(MonsterAI __instance, Character ___m_character, ref Character ___m_targetCreature, ref StaticTarget ___m_targetStatic, float ___m_timeSinceSensedTargetCreature, ref float ___m_timeSinceAttacking, ref float ___m_updateTargetTimer, float ___m_timeSinceHurt) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) AiState aiState = AiState.Get(__instance); Character val = ___m_targetCreature; if ((Object)(object)val == (Object)null || !val.IsPlayer()) { aiState.ChaseTarget = null; return; } if ((Object)(object)aiState.ChaseTarget != (Object)(object)val) { aiState.ChaseTarget = val; aiState.ChaseStart = ((Component)__instance).transform.position; aiState.Returning = false; aiState.UnreachableSince = -1f; } if (!Plugin.Affects(___m_character, aiState) || (((BaseAI)__instance).HuntPlayer() && !Plugin.AffectRaidMonsters.Value)) { return; } CreatureSettings settings = aiState.Settings; float giveUpWhenNotSensed = settings.GiveUpWhenNotSensed; float giveUpWhenNoCombat = settings.GiveUpWhenNoCombat; float maxChaseDistance = settings.MaxChaseDistance; bool flag = ___m_timeSinceHurt <= 5f || ___m_timeSinceAttacking <= 5f; string text = null; if (giveUpWhenNotSensed > 0f && ___m_timeSinceSensedTargetCreature > giveUpWhenNotSensed) { text = "couldn't see or hear the target"; } else if (giveUpWhenNoCombat > 0f && ___m_timeSinceAttacking > giveUpWhenNoCombat && ___m_timeSinceHurt > giveUpWhenNoCombat) { text = "no combat for " + giveUpWhenNoCombat + " s"; } else if (!flag && maxChaseDistance > 0f && Vector3.Distance(((Component)__instance).transform.position, aiState.ChaseStart) > maxChaseDistance) { text = "chased farther than " + maxChaseDistance + " m"; } else if (IsUnreachable(__instance, ___m_character, aiState, val, settings.GiveUpWhenUnreachable) && !flag) { text = "no path to the target"; } if (text != null) { Plugin.Debug(aiState.PrefabName + " gave up the chase: " + text); setAlerted.Invoke(__instance, new object[1] { false }); ___m_targetCreature = null; ___m_targetStatic = null; ___m_timeSinceAttacking = 0f; ___m_updateTargetTimer = Mathf.Max(settings.IgnoreAfterGiveUp, 0f); aiState.ChaseTarget = null; float num = Utils.DistanceXZ(((Component)__instance).transform.position, aiState.ChaseStart); if (settings.ReturnAfterGiveUp && num > 4f) { aiState.Returning = true; aiState.ReturnTarget = aiState.ChaseStart; aiState.ReturnBestDistance = num; aiState.ReturnProgressTime = Time.time; } } } } [HarmonyPatch(typeof(BaseAI), "IdleMovement")] internal static class ReturnPatch { internal const float ArriveDistance = 2f; private const float StuckTime = 4f; private const float Progress = 1f; private static readonly Func<BaseAI, float, Vector3, float, bool, bool> moveTo = AccessTools.MethodDelegate<Func<BaseAI, float, Vector3, float, bool, bool>>(AccessTools.Method(typeof(BaseAI), "MoveTo", new Type[4] { typeof(float), typeof(Vector3), typeof(float), typeof(bool) }, (Type[])null), (object)null, true); private static bool Prefix(BaseAI __instance, float __0) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) MonsterAI val = (MonsterAI)(object)((__instance is MonsterAI) ? __instance : null); if ((Object)(object)val == (Object)null) { return true; } AiState aiState = AiState.Get(val); if (!aiState.Returning) { return true; } float num = Utils.DistanceXZ(((Component)val).transform.position, aiState.ReturnTarget); if (num < aiState.ReturnBestDistance - 1f) { aiState.ReturnBestDistance = num; aiState.ReturnProgressTime = Time.time; } if (num <= 2f || Time.time - aiState.ReturnProgressTime > 4f) { aiState.Returning = false; return true; } moveTo((BaseAI)(object)val, __0, aiState.ReturnTarget, 2f, arg5: false); return false; } } internal static class SleepPatch { [HarmonyPatch(typeof(MonsterAI), "UpdateTarget")] private static class TrackUpdatingCreature { private static void Prefix(MonsterAI __instance) { updating = __instance; } private static void Postfix() { updating = null; } } [HarmonyPatch(typeof(Player), "OnTargeted")] private static class IgnoreTamingCreatures { private static bool Prefix() { if (!((Object)(object)updating == (Object)null) && Plugin.TamingDoesntBlockSleep.Value) { return !IsBeingTamed(updating); } return true; } } private static MonsterAI updating; private static readonly Func<Tameable, int> getTameness = AccessTools.MethodDelegate<Func<Tameable, int>>(AccessTools.Method(typeof(Tameable), "GetTameness", (Type[])null, (Type[])null), (object)null, true); private static bool IsBeingTamed(MonsterAI ai) { Tameable component = ((Component)ai).GetComponent<Tameable>(); if ((Object)(object)component != (Object)null && !component.IsTamed() && !component.IsHungry()) { return getTameness(component) > 0; } return false; } }