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 TameLeash v1.8.0
Dupo_TameLeash.dll
Decompiled a day agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; 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("Dupo_TameLeash")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.8.0.0")] [assembly: AssemblyInformationalVersion("1.8.0+738f4fc2bc6e6fba2bed215183d9404e70e8deb3")] [assembly: AssemblyProduct("Dupo_TameLeash")] [assembly: AssemblyTitle("Dupo_TameLeash")] [assembly: AssemblyVersion("1.8.0.0")] [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 Dupo.TameLeash { [BepInPlugin("dupo.TameLeash", "Dupo_TameLeash", "1.8.0")] public class Plugin : BaseUnityPlugin { public const string ModGuid = "dupo.TameLeash"; public const string ModName = "Dupo_TameLeash"; public const string ModVersion = "1.8.0"; private const string FollowDistanceModGuid = "blacks7ar.FollowDistance"; internal static ManualLogSource Log; internal static ConfigEntry<float> FollowDistance; internal static ConfigEntry<bool> KeepAway; internal static ConfigEntry<float> KeepAwayDistance; internal static ConfigEntry<float> KeepAwayDelay; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<float> GuardRadius; internal static ConfigEntry<float> ReleaseRadius; internal static ConfigEntry<bool> DebugLog; internal static ConfigEntry<bool> DefenseEnabled; internal static ConfigEntry<float> BaseReduction; internal static ConfigEntry<float> MaxReduction; internal static ConfigEntry<bool> AssistEnabled; internal static ConfigEntry<float> AssistDuration; internal static ConfigEntry<bool> HandOverToFollowed; internal static ConfigEntry<bool> NameplatesAlwaysShow; internal static ConfigEntry<float> NameplateDistance; internal static ConfigEntry<string> SleepingLabel; internal static ConfigEntry<string> SleepingLabelColor; internal static ConfigEntry<PassThroughMode> PassThroughTames; private readonly Harmony _harmony = new Harmony("dupo.TameLeash"); private float _nextPassThroughUpdate; private void Awake() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: 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_0130: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Expected O, but got Unknown //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Expected O, but got Unknown //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Expected O, but got Unknown //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Expected O, but got Unknown //IL_0301: Unknown result type (might be due to invalid IL or missing references) //IL_030b: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; FollowDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Follow", "Follow Distance", 9f, new ConfigDescription("When not fighting, a tame following you stops this many meters away from you. Vanilla is 3. Changes apply immediately, no restart needed. If you lower it, check Keep Away Distance: it must stay below Follow Distance minus 1.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 30f), Array.Empty<object>())); KeepAway = ((BaseUnityPlugin)this).Config.Bind<bool>("Follow", "Keep Away", true, "When not fighting, a tame that stays too close to you (see Keep Away Distance and Keep Away Delay) walks away until it is 1 m inside Follow Distance. Useful after portals and teleports drop it at your feet. Only works if Follow Distance minus 1 is greater than Keep Away Distance."); KeepAwayDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Follow", "Keep Away Distance", 4f, new ConfigDescription("A tame closer than this distance (meters) to you counts as too close. Must be less than Follow Distance minus 1, otherwise Keep Away is off (e.g. with Follow Distance 5, use 3 or less).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 20f), Array.Empty<object>())); KeepAwayDelay = ((BaseUnityPlugin)this).Config.Bind<float>("Follow", "Keep Away Delay", 3f, new ConfigDescription("How many seconds a tame must stay too close before it walks away. Leaves you time to mount, pet or feed it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>())); Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Turns the guard radius on or off."); GuardRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Guard Radius", 20f, new ConfigDescription("A tame following you only picks enemies within this distance (meters) of you.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 50f), Array.Empty<object>())); ReleaseRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Release Radius", 28f, new ConfigDescription("A tame only gives up its current target once that target is farther than this distance (meters) from you. If set lower than Guard Radius, Guard Radius is used instead.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 60f), Array.Empty<object>())); DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Debug Log", false, "Every 2 seconds, writes to LogOutput.log what each following tame sees and decides. Only meant for troubleshooting: keep it off during normal play."); DefenseEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Defense", "Enabled", true, "Tamed creatures take less damage, more as the world progresses (see the other Defense settings). Applies to all tames, following or not. In multiplayer every player should use the same values: the reduction is applied by whoever owns the creature at that moment."); BaseReduction = ((BaseUnityPlugin)this).Config.Bind<float>("Defense", "Base Reduction", 0f, new ConfigDescription("Damage reduction (percent) that tames have before Moder is defeated.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 90f), Array.Empty<object>())); MaxReduction = ((BaseUnityPlugin)this).Config.Bind<float>("Defense", "Max Reduction", 50f, new ConfigDescription("Damage reduction (percent) that tames have once all of Moder, Yagluth, The Queen and Fader are defeated in the world. Each of those bosses adds a quarter of the gap between Base Reduction and this value (defaults: 12.5% per boss).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 90f), Array.Empty<object>())); AssistEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Assist", "Enabled", true, "[Experimental] When you hit an enemy, the tames following you attack that enemy, even if they were fighting another one or had not noticed it yet. Only enemies within Release Radius of you."); AssistDuration = ((BaseUnityPlugin)this).Config.Bind<float>("Assist", "Assist Duration", 10f, new ConfigDescription("After your last hit on an enemy, tames keep focusing it for this many seconds, then they go back to picking enemies on their own.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 60f), Array.Empty<object>())); HandOverToFollowed = ((BaseUnityPlugin)this).Config.Bind<bool>("Multiplayer", "Hand Over To Followed Player", true, "[Experimental] In multiplayer, a creature is run by one player's game at a time, often not the one it follows. With this on, a tame following a player is handed over to that player's game, so it uses that player's settings, Assist works, and it reacts without lag. Ridden tames are never handed over. Works only if the game currently running the tame has TameLeash installed."); NameplatesAlwaysShow = ((BaseUnityPlugin)this).Config.Bind<bool>("Nameplates", "Always Show", true, "The name and health bar of the tames following you stay visible (vanilla hides them shortly after you stop looking at a creature, and never shows them beyond 10 m)."); NameplateDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Nameplates", "Show Distance", 30f, new ConfigDescription("Up to this distance (meters) from you, the nameplates of your following tames are shown. Vanilla is 10.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 60f), Array.Empty<object>())); SleepingLabel = ((BaseUnityPlugin)this).Config.Bind<string>("Nameplates", "Sleeping Label", "[zZz]", "Text shown next to the name of a sleeping tame. With BetterTames, a downed (recovering) tame is asleep. Leave empty to show nothing."); SleepingLabelColor = ((BaseUnityPlugin)this).Config.Bind<string>("Nameplates", "Sleeping Label Color", "#7FE0FF", "Color of the Sleeping Label, as #RRGGBB. The default light blue stands out from BetterTames' yellow (downed) and blue (protected) names."); PassThroughTames = ((BaseUnityPlugin)this).Config.Bind<PassThroughMode>("Collision", "Pass Through Tames", PassThroughMode.All, "Players walk through tamed creatures instead of bumping into them, so tames can't block doors, corridors or bridges. Tames still collide with enemies, walls and each other, and you can still hit, pet and ride them. All = every tamed creature. Following = only tames following that player. Off = vanilla."); _harmony.PatchAll(typeof(Patches)); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Dupo_TameLeash 1.8.0 loaded."); } private void Start() { if (DebugLog.Value) { string[] array = new string[2] { "Follow", "FindEnemy" }; foreach (string text in array) { Patches patchInfo = Harmony.GetPatchInfo((MethodBase)AccessTools.Method(typeof(BaseAI), text, (Type[])null, (Type[])null)); ((BaseUnityPlugin)this).Logger.LogInfo((object)((patchInfo == null) ? ("BaseAI." + text + ": NOT patched") : ("BaseAI." + text + " patched by: " + string.Join(", ", patchInfo.Owners) + " " + $"(prefixes={patchInfo.Prefixes.Count}, postfixes={patchInfo.Postfixes.Count}, transpilers={patchInfo.Transpilers.Count})"))); } } if (KeepAway.Value && FollowDistance.Value - 1f <= KeepAwayDistance.Value) { ((BaseUnityPlugin)this).Logger.LogWarning((object)($"Keep Away is on but can't work: Follow Distance ({FollowDistance.Value:0.#}) minus 1 must be greater than " + $"Keep Away Distance ({KeepAwayDistance.Value:0.#}). Lower Keep Away Distance to " + $"{Mathf.Max(1f, Mathf.Ceil(FollowDistance.Value - 1f) - 1f):0} or less, or raise Follow Distance.")); } if (Chainloader.PluginInfos.ContainsKey("blacks7ar.FollowDistance")) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"FollowDistance is also installed. TameLeash now handles the follow distance itself (see 'Follow Distance' in its config) and overrides FollowDistance, which can be removed."); } } private void Update() { float time = Time.time; if (!(time < _nextPassThroughUpdate)) { _nextPassThroughUpdate = time + 0.2f; PassThrough.Update(); } } private void OnDestroy() { PassThrough.RestoreAll(); _harmony.UnpatchSelf(); } } internal enum PassThroughMode { Off, Following, All } internal static class PassThrough { internal const float UpdateInterval = 0.2f; private static readonly FieldRef<Character, CapsuleCollider> CharacterCollider = AccessTools.FieldRefAccess<Character, CapsuleCollider>("m_collider"); private static readonly FieldRef<Character, ZNetView> CharacterNView = AccessTools.FieldRefAccess<Character, ZNetView>("m_nview"); private static Dictionary<long, KeyValuePair<Collider, Collider>> _ignored = new Dictionary<long, KeyValuePair<Collider, Collider>>(); private static Dictionary<long, KeyValuePair<Collider, Collider>> _wanted = new Dictionary<long, KeyValuePair<Collider, Collider>>(); private static readonly List<Collider> TameColliders = new List<Collider>(); private static int _lastLoggedCount = -1; private static long PairKey(Collider a, Collider b) { return ((long)((Object)a).GetInstanceID() << 32) ^ (uint)((Object)b).GetInstanceID(); } internal static void Update() { _wanted.Clear(); PassThroughMode value = Plugin.PassThroughTames.Value; List<Player> allPlayers = Player.GetAllPlayers(); if (value != PassThroughMode.Off && allPlayers.Count > 0) { foreach (Character allCharacter in Character.GetAllCharacters()) { if ((Object)(object)allCharacter == (Object)null || allCharacter.IsPlayer() || !allCharacter.IsTamed() || allCharacter.IsDead()) { continue; } string text = null; if (value == PassThroughMode.Following) { ZNetView val = CharacterNView.Invoke(allCharacter); if ((Object)(object)val == (Object)null || !val.IsValid()) { continue; } text = val.GetZDO().GetString(ZDOVars.s_follow, ""); if (string.IsNullOrEmpty(text)) { continue; } } TameColliders.Clear(); ((Component)allCharacter).GetComponentsInChildren<Collider>(false, TameColliders); foreach (Player item in allPlayers) { if ((Object)(object)item == (Object)null || (text != null && text != item.GetPlayerName())) { continue; } Collider val2 = (Collider)(object)CharacterCollider.Invoke((Character)(object)item); if ((Object)(object)val2 == (Object)null) { continue; } foreach (Collider tameCollider in TameColliders) { if ((Object)(object)tameCollider != (Object)null && !tameCollider.isTrigger && tameCollider.enabled) { _wanted[PairKey(val2, tameCollider)] = new KeyValuePair<Collider, Collider>(val2, tameCollider); } } } } } foreach (KeyValuePair<Collider, Collider> value3 in _wanted.Values) { Physics.IgnoreCollision(value3.Key, value3.Value, true); } foreach (KeyValuePair<long, KeyValuePair<Collider, Collider>> item2 in _ignored) { if (!_wanted.ContainsKey(item2.Key)) { Collider key = item2.Value.Key; Collider value2 = item2.Value.Value; if ((Object)(object)key != (Object)null && (Object)(object)value2 != (Object)null) { Physics.IgnoreCollision(key, value2, false); } } } Dictionary<long, KeyValuePair<Collider, Collider>> ignored = _ignored; _ignored = _wanted; _wanted = ignored; if (Plugin.DebugLog.Value && _ignored.Count != _lastLoggedCount) { _lastLoggedCount = _ignored.Count; Plugin.Log.LogInfo((object)$"[COLLISION] mode {value}: {_ignored.Count} player/tame collider pairs pass through"); } } internal static void RestoreAll() { foreach (KeyValuePair<Collider, Collider> value in _ignored.Values) { if ((Object)(object)value.Key != (Object)null && (Object)(object)value.Value != (Object)null) { Physics.IgnoreCollision(value.Key, value.Value, false); } } _ignored.Clear(); } } internal static class Patches { private sealed class FollowState { public float TooCloseSince = -1f; public bool Retreating; public float NextLog; public float NextHandOverCheck; public Character LastAssistTarget; } private sealed class DebugState { public float NextLog; public Character TargetBefore; } private static readonly FieldRef<MonsterAI, Character> TargetCreature = AccessTools.FieldRefAccess<MonsterAI, Character>("m_targetCreature"); private static readonly FieldRef<BaseAI, Character> CharacterRef = AccessTools.FieldRefAccess<BaseAI, Character>("m_character"); private static readonly FieldRef<BaseAI, ZNetView> NViewRef = AccessTools.FieldRefAccess<BaseAI, ZNetView>("m_nview"); private static readonly FieldRef<MonsterAI, StaticTarget> TargetStatic = AccessTools.FieldRefAccess<MonsterAI, StaticTarget>("m_targetStatic"); private static readonly FieldRef<MonsterAI, float> TimeSinceSensedTarget = AccessTools.FieldRefAccess<MonsterAI, float>("m_timeSinceSensedTargetCreature"); private static readonly Action<BaseAI, bool> SetAlerted = AccessTools.MethodDelegate<Action<BaseAI, bool>>(AccessTools.Method(typeof(BaseAI), "SetAlerted", (Type[])null, (Type[])null), (object)null, true); 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 Func<BaseAI, bool> FoundPath = AccessTools.MethodDelegate<Func<BaseAI, bool>>(AccessTools.Method(typeof(BaseAI), "FoundPath", (Type[])null, (Type[])null), (object)null, true); private static readonly ConditionalWeakTable<BaseAI, FollowState> FollowStates = new ConditionalWeakTable<BaseAI, FollowState>(); private static readonly ConditionalWeakTable<MonsterAI, DebugState> DebugStates = new ConditionalWeakTable<MonsterAI, DebugState>(); private static Character _assistTarget; private static float _assistUntil; private static readonly FieldRef<Character, ZNetView> CharacterNView = AccessTools.FieldRefAccess<Character, ZNetView>("m_nview"); private static readonly FieldInfo HudsField = AccessTools.Field(typeof(EnemyHud), "m_huds"); private static readonly Type HudDataType = AccessTools.Inner(typeof(EnemyHud), "HudData"); private static readonly FieldInfo HudHoverTimer = AccessTools.Field(HudDataType, "m_hoverTimer"); private static readonly FieldInfo HudGui = AccessTools.Field(HudDataType, "m_gui"); private static readonly FieldInfo HudName = AccessTools.Field(HudDataType, "m_name"); private static readonly string[] ProgressionBossKeys = new string[4] { "defeated_dragon", "defeated_goblinking", "defeated_queen", "defeated_fader" }; private static float ReleaseRadius => Mathf.Max(Plugin.ReleaseRadius.Value, Plugin.GuardRadius.Value); private static void DebugEvent(BaseAI ai, Character self, string message) { if (Plugin.DebugLog.Value) { Plugin.Log.LogInfo((object)$"[FOLLOW] [{self.m_name} #{((Object)ai).GetInstanceID()}] {message}"); } } [HarmonyPrefix] [HarmonyPatch(typeof(BaseAI), "Follow")] private static bool Follow_Prefix(BaseAI __instance, GameObject go, float dt) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: 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_0159: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0332: Unknown result type (might be due to invalid IL or missing references) //IL_0337: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)go == (Object)null) { return true; } Character val = CharacterRef.Invoke(__instance); if ((Object)(object)val == (Object)null || !val.IsTamed()) { return true; } FollowState orCreateValue = FollowStates.GetOrCreateValue(__instance); float time = Time.time; float value = Plugin.FollowDistance.Value; Vector3 position = go.transform.position; Vector3 position2 = ((Component)__instance).transform.position; float num = Vector3.Distance(position, position2); float value2 = Plugin.KeepAwayDistance.Value; float num2 = value - 1f; if (!Plugin.KeepAway.Value || !(num2 > value2)) { orCreateValue.Retreating = false; orCreateValue.TooCloseSince = -1f; } else if (!orCreateValue.Retreating) { if (num < value2) { if (orCreateValue.TooCloseSince < 0f) { orCreateValue.TooCloseSince = time; } if (time - orCreateValue.TooCloseSince >= Plugin.KeepAwayDelay.Value) { orCreateValue.Retreating = true; DebugEvent(__instance, val, $"KEEP AWAY start: {num:0.0}m from you, walking out to {num2:0.0}m"); } } else { orCreateValue.TooCloseSince = -1f; } } if (orCreateValue.Retreating && num >= num2) { orCreateValue.Retreating = false; orCreateValue.TooCloseSince = -1f; DebugEvent(__instance, val, $"KEEP AWAY done: {num:0.0}m from you"); } string text; if (orCreateValue.Retreating) { Vector3 val2 = position2 - position; val2.y = 0f; if (((Vector3)(ref val2)).sqrMagnitude < 0.01f) { val2 = -((Component)__instance).transform.forward; } val2.y = 0f; Vector3 arg = position + ((Vector3)(ref val2)).normalized * num2; if (MoveTo(__instance, dt, arg, 0.5f, arg5: false)) { orCreateValue.Retreating = false; orCreateValue.TooCloseSince = ((num < value2) ? time : (-1f)); if (FoundPath(__instance)) { DebugEvent(__instance, val, $"KEEP AWAY done: reached its spot, {num:0.0}m from you"); } else { DebugEvent(__instance, val, $"KEEP AWAY gave up: no path, {num:0.0}m from you, on ground={val.IsOnGround()}, " + ((orCreateValue.TooCloseSince >= 0f) ? $"retrying in {Plugin.KeepAwayDelay.Value:0}s" : "no longer too close")); } } text = "KEEP AWAY"; } else if (num < value) { __instance.StopMoving(); text = ((orCreateValue.TooCloseSince >= 0f) ? $"STOP (too close for {time - orCreateValue.TooCloseSince:0.0}s)" : "STOP"); } else { bool flag = num > value + 7f; MoveTo(__instance, dt, position, 0f, flag); text = (flag ? "RUN" : "WALK"); } if (Plugin.DebugLog.Value && time >= orCreateValue.NextLog) { orCreateValue.NextLog = time + 2f; ManualLogSource log = Plugin.Log; string text2 = $"[FOLLOW] [{val.m_name} #{((Object)__instance).GetInstanceID()}] {num:0.0}m from you, "; object arg2 = value; string arg3 = text; Vector3 velocity = val.GetVelocity(); log.LogInfo((object)(text2 + $"stop distance {arg2:0.0}m -> {arg3}, actual speed {((Vector3)(ref velocity)).magnitude:0.0} m/s")); } return false; } private static bool TryGetGuardCenter(BaseAI ai, out Character self, out Vector3 center) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) self = null; center = default(Vector3); MonsterAI val = (MonsterAI)(object)((ai is MonsterAI) ? ai : null); if (val == null) { return false; } self = CharacterRef.Invoke(ai); if ((Object)(object)self == (Object)null || !self.IsTamed()) { return false; } GameObject followTarget = val.GetFollowTarget(); if ((Object)(object)followTarget == (Object)null) { return false; } center = followTarget.transform.position; return true; } [HarmonyPrefix] [HarmonyPatch(typeof(BaseAI), "FindEnemy")] private static bool FindEnemy_Prefix(BaseAI __instance, ref Character __result) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0082: 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) //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) if (!Plugin.Enabled.Value || !TryGetGuardCenter(__instance, out var self, out var center)) { return true; } float value = Plugin.GuardRadius.Value; Vector3 position = ((Component)__instance).transform.position; Character val = null; float num = float.MaxValue; foreach (Character allCharacter in Character.GetAllCharacters()) { if ((Object)(object)allCharacter == (Object)null || allCharacter.IsDead() || allCharacter.m_aiSkipTarget || !BaseAI.IsEnemy(self, allCharacter) || Vector3.Distance(((Component)allCharacter).transform.position, center) > value) { continue; } BaseAI baseAI = allCharacter.GetBaseAI(); if ((!((Object)(object)baseAI != (Object)null) || !baseAI.IsSleeping()) && __instance.CanSenseTarget(allCharacter)) { float num2 = Vector3.Distance(((Component)allCharacter).transform.position, position); if (num2 < num) { val = allCharacter; num = num2; } } } __result = val; return false; } [HarmonyPrefix] [HarmonyPatch(typeof(MonsterAI), "UpdateTarget")] private static void UpdateTarget_Prefix(MonsterAI __instance) { if (Plugin.DebugLog.Value) { DebugStates.GetOrCreateValue(__instance).TargetBefore = TargetCreature.Invoke(__instance); } } [HarmonyPostfix] [HarmonyPatch(typeof(MonsterAI), "UpdateTarget")] private static void UpdateTarget_Postfix(MonsterAI __instance, ref bool canHearTarget, ref bool canSeeTarget) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) if (TryGetGuardCenter((BaseAI)(object)__instance, out var self, out var center) && (!Plugin.HandOverToFollowed.Value || !HandOverToFollowedPlayer(__instance, self))) { ApplyAssist(__instance, self, center, ref canHearTarget, ref canSeeTarget); Character val = TargetCreature.Invoke(__instance); bool dropped = false; if (Plugin.Enabled.Value && (Object)(object)val != (Object)null && Vector3.Distance(((Component)val).transform.position, center) > ReleaseRadius) { TargetCreature.Invoke(__instance) = null; canHearTarget = false; canSeeTarget = false; dropped = true; } if (Plugin.DebugLog.Value) { DebugReport(__instance, self, center, val, dropped); } } } private static bool HandOverToFollowedPlayer(MonsterAI ai, Character self) { FollowState orCreateValue = FollowStates.GetOrCreateValue((BaseAI)(object)ai); float time = Time.time; if (time < orCreateValue.NextHandOverCheck) { return false; } orCreateValue.NextHandOverCheck = time + 1f; GameObject followTarget = ai.GetFollowTarget(); Player val = (((Object)(object)followTarget != (Object)null) ? followTarget.GetComponent<Player>() : null); ZNetView val2 = NViewRef.Invoke((BaseAI)(object)ai); ZDO val3 = (((Object)(object)val2 != (Object)null && val2.IsValid()) ? val2.GetZDO() : null); if ((Object)(object)val == (Object)null || val3 == null || !val3.IsOwner()) { return false; } long owner = ((Character)val).GetOwner(); if (owner == 0L || owner == val3.GetOwner()) { return false; } if (val3.GetString(ZDOVars.s_follow, "") != val.GetPlayerName()) { return false; } Tameable component = ((Component)self).GetComponent<Tameable>(); if ((Object)(object)component != (Object)null && (Object)(object)component.m_saddle != (Object)null && component.m_saddle.HaveValidUser()) { return false; } val3.SetOwner(owner); DebugEvent((BaseAI)(object)ai, self, "HANDED OVER to the game of " + val.GetPlayerName() + ", the player it follows"); return true; } [HarmonyPrefix] [HarmonyPatch(typeof(Character), "Damage")] private static void Damage_Prefix(Character __instance, HitData hit) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.AssistEnabled.Value || hit == null || (Object)(object)__instance == (Object)null) { return; } Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && !((Object)(object)__instance == (Object)(object)localPlayer) && !(hit.m_attacker != ((Character)localPlayer).GetZDOID()) && !__instance.IsPlayer() && !__instance.IsTamed() && !__instance.IsDead()) { if ((Object)(object)_assistTarget != (Object)(object)__instance && Plugin.DebugLog.Value) { Plugin.Log.LogInfo((object)("[ASSIST] you hit " + __instance.m_name + ": tames following you will focus it")); } _assistTarget = __instance; _assistUntil = Time.time + Plugin.AssistDuration.Value; } } private static void ApplyAssist(MonsterAI ai, Character self, Vector3 center, ref bool canHearTarget, ref bool canSeeTarget) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.AssistEnabled.Value) { return; } Character assistTarget = _assistTarget; if ((Object)(object)assistTarget == (Object)null) { return; } if (Time.time > _assistUntil || assistTarget.IsDead()) { _assistTarget = null; return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)ai.GetFollowTarget() != (Object)(object)((Component)localPlayer).gameObject || Vector3.Distance(((Component)assistTarget).transform.position, center) > ReleaseRadius || !BaseAI.IsEnemy(self, assistTarget) || assistTarget.AboveOrInLava()) { return; } if (Plugin.DebugLog.Value) { FollowState orCreateValue = FollowStates.GetOrCreateValue((BaseAI)(object)ai); if ((Object)(object)orCreateValue.LastAssistTarget != (Object)(object)assistTarget) { orCreateValue.LastAssistTarget = assistTarget; DebugEvent((BaseAI)(object)ai, self, "ASSIST: attacking your target " + Describe(assistTarget, center)); } } TargetCreature.Invoke(ai) = assistTarget; TargetStatic.Invoke(ai) = null; TimeSinceSensedTarget.Invoke(ai) = 0f; canHearTarget = true; canSeeTarget = ((BaseAI)ai).CanSeeTarget(assistTarget); if (!((BaseAI)ai).IsAlerted()) { SetAlerted((BaseAI)(object)ai, arg2: true); } } private static bool IsLocalPlayersFollower(Character c, out ZDO zdo) { zdo = null; Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)c == (Object)null || c.IsPlayer() || !c.IsTamed()) { return false; } ZNetView val = CharacterNView.Invoke(c); if ((Object)(object)val == (Object)null || !val.IsValid()) { return false; } zdo = val.GetZDO(); return zdo.GetString(ZDOVars.s_follow, "") == localPlayer.GetPlayerName(); } [HarmonyPostfix] [HarmonyPatch(typeof(EnemyHud), "TestShow")] private static void TestShow_Postfix(Character c, ref bool __result) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (!__result && Plugin.NameplatesAlwaysShow.Value) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && IsLocalPlayersFollower(c, out var _) && Vector3.Distance(((Component)c).transform.position, ((Component)localPlayer).transform.position) <= Plugin.NameplateDistance.Value) { __result = true; } } } [HarmonyPrefix] [HarmonyPatch(typeof(EnemyHud), "UpdateHuds")] private static void UpdateHuds_Prefix(EnemyHud __instance) { if (!Plugin.NameplatesAlwaysShow.Value || !(HudsField.GetValue(__instance) is IDictionary dictionary)) { return; } foreach (DictionaryEntry item in dictionary) { object key = item.Key; Character val = (Character)((key is Character) ? key : null); if (val != null && IsLocalPlayersFollower(val, out var _)) { HudHoverTimer.SetValue(item.Value, 0f); } } } [HarmonyPostfix] [HarmonyPatch(typeof(EnemyHud), "UpdateHuds")] private static void UpdateHuds_Postfix(EnemyHud __instance) { string value = Plugin.SleepingLabel.Value; if (!Plugin.NameplatesAlwaysShow.Value || string.IsNullOrEmpty(value) || !(HudsField.GetValue(__instance) is IDictionary dictionary)) { return; } foreach (DictionaryEntry item in dictionary) { object key = item.Key; Character val = (Character)((key is Character) ? key : null); if (val == null || !IsLocalPlayersFollower(val, out var zdo) || !zdo.GetBool(ZDOVars.s_sleeping, false)) { continue; } object? value2 = HudGui.GetValue(item.Value); GameObject val2 = (GameObject)((value2 is GameObject) ? value2 : null); if (val2 != null && val2.activeSelf) { object? value3 = HudName.GetValue(item.Value); TMP_Text val3 = (TMP_Text)((value3 is TMP_Text) ? value3 : null); if (val3 != null && !val3.text.Contains(value)) { TMP_Text val4 = val3; val4.text = val4.text + " <b><color=" + Plugin.SleepingLabelColor.Value + ">" + value + "</color></b>"; } } } } internal static float TameDamageReduction(out int bosses) { bosses = 0; ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance != (Object)null) { string[] progressionBossKeys = ProgressionBossKeys; foreach (string text in progressionBossKeys) { if (instance.GetGlobalKey(text)) { bosses++; } } } float value = Plugin.BaseReduction.Value; float num = Mathf.Max(Plugin.MaxReduction.Value, value); return Mathf.Clamp(value + (num - value) * (float)bosses / (float)ProgressionBossKeys.Length, 0f, 90f) / 100f; } [HarmonyPrefix] [HarmonyPatch(typeof(Character), "RPC_Damage")] private static void RPC_Damage_Prefix(Character __instance, HitData hit) { if (!Plugin.DefenseEnabled.Value || hit == null || (Object)(object)__instance == (Object)null || !__instance.IsTamed()) { return; } int bosses; float num = TameDamageReduction(out bosses); if (!(num <= 0f)) { float num2 = (Plugin.DebugLog.Value ? hit.GetTotalDamage() : 0f); ((DamageTypes)(ref hit.m_damage)).Modify(1f - num); if (Plugin.DebugLog.Value) { Plugin.Log.LogInfo((object)($"[DEFENSE] [{__instance.m_name} #{((Object)__instance).GetInstanceID()}] hit {num2:0.0} -> {hit.GetTotalDamage():0.0} " + $"(-{num * 100f:0}%, bosses defeated={bosses})")); } } } private static string Describe(Character c, Vector3 center) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)c == (Object)null)) { return $"{c.m_name}@{Vector3.Distance(((Component)c).transform.position, center):0.0}m from you"; } return "none"; } private static void DebugReport(MonsterAI ai, Character self, Vector3 center, Character targetAfterGame, bool dropped) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: 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_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_0290: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) DebugState orCreateValue = DebugStates.GetOrCreateValue(ai); float time = Time.time; Character targetBefore = orCreateValue.TargetBefore; Character val = TargetCreature.Invoke(ai); string text = string.Format("[{0}] [{1} #{2}]", Plugin.Enabled.Value ? "MOD" : "VANILLA", self.m_name, ((Object)ai).GetInstanceID()); if (dropped) { Plugin.Log.LogInfo((object)(text + " DROPPED target beyond release radius: " + Describe(targetAfterGame, center))); } else if ((Object)(object)targetBefore != (Object)(object)val) { if ((Object)(object)val != (Object)null) { Plugin.Log.LogInfo((object)(text + " NEW target: " + Describe(val, center))); } else { Plugin.Log.LogInfo((object)(text + " target lost by the game (dead/not an enemy/on lava/timeout): " + Describe(targetBefore, center))); } } if (time < orCreateValue.NextLog) { return; } orCreateValue.NextLog = time + 2f; int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; Character c = null; float num7 = float.MaxValue; float value = Plugin.GuardRadius.Value; foreach (Character allCharacter in Character.GetAllCharacters()) { if ((Object)(object)allCharacter == (Object)null || allCharacter.IsDead() || !BaseAI.IsEnemy(self, allCharacter)) { continue; } num++; float num8 = Vector3.Distance(((Component)allCharacter).transform.position, center); BaseAI baseAI = allCharacter.GetBaseAI(); bool flag = (Object)(object)baseAI != (Object)null && baseAI.IsSleeping(); bool flag2 = ((BaseAI)ai).CanSenseTarget(allCharacter); if (allCharacter.AboveOrInLava()) { num5++; } if (num8 <= value) { num2++; if (flag) { num4++; } else if (flag2) { num3++; } } else if (flag2 && !flag) { num6++; if (num8 < num7) { c = allCharacter; num7 = num8; } } } Plugin.Log.LogInfo((object)($"{text} {Vector3.Distance(((Component)ai).transform.position, center):0.0}m from you, alerted={((BaseAI)ai).IsAlerted()}, " + $"target={Describe(val, center)} | enemies loaded={num}, within {value:0}m={num2} " + $"(sensed={num3}, asleep={num4}), on lava={num5}, " + $"sensed outside radius={num6} (closest: {Describe(c, center)})")); } } }