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 AnimalFeedGuard v2.0.3
BepInEx/plugins/AnimalFeedGuard/AnimalFeedGuard.dll
Decompiled a month agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("AnimalFeedGuard")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.0.3.0")] [assembly: AssemblyInformationalVersion("2.0.3+7c5f8b63416d0ff476aaf75410aaeaf95063b7b8")] [assembly: AssemblyProduct("AnimalFeedGuard")] [assembly: AssemblyTitle("AnimalFeedGuard")] [assembly: AssemblyVersion("2.0.3.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 AnimalFeedGuard { internal static class FeedRules { internal static bool WithinRadius(float squaredDistance, float radius) { if (radius > 0f && squaredDistance >= 0f) { return squaredDistance <= radius * radius; } return false; } internal static bool SameFood(string? food, string? animalFood) { if (!string.IsNullOrEmpty(food)) { return string.Equals(food, animalFood, StringComparison.Ordinal); } return false; } internal static bool HasFood(string? food, IEnumerable<string?> diet) { foreach (string item in diet) { if (SameFood(food, item)) { return true; } } return false; } internal static bool Protects(bool alive, bool tamed, float squaredDistance, float radius, string? food, IEnumerable<string?> diet) { if (!alive || !tamed || !WithinRadius(squaredDistance, radius)) { return false; } return HasFood(food, diet); } } [BepInPlugin("com.ziluck.valheim.animalfeedguard", "AnimalFeedGuard", "2.0.3")] public sealed class Plugin : BaseUnityPlugin { private readonly struct FeedingAnimal { internal readonly Character Character; internal readonly MonsterAI AI; internal FeedingAnimal(Character character, MonsterAI ai) { Character = character; AI = ai; } } [HarmonyPatch(typeof(Player), "AutoPickup")] private static class AutomaticPickupPatch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { FieldInfo field = AccessTools.Field(typeof(ItemDrop), "m_autoPickup"); MethodInfo filter = AccessTools.Method(typeof(Plugin), "CanAutoPickup", (Type[])null, (Type[])null); List<CodeInstruction> list = instructions.ToList(); if (list.Count((CodeInstruction i) => CodeInstructionExtensions.LoadsField(i, field, false)) != 1) { throw new InvalidOperationException("Expected one ItemDrop.m_autoPickup check. Game version or another pickup mod changed this method."); } foreach (CodeInstruction item in list) { if (CodeInstructionExtensions.LoadsField(item, field, false)) { item.opcode = OpCodes.Call; item.operand = filter; } yield return item; } } } public const string Guid = "com.ziluck.valheim.animalfeedguard"; private static ConfigEntry<bool> protectionEnabled = null; private static ConfigEntry<float> radius = null; private static ConfigEntry<bool> diagnosticLogging = null; private static ManualLogSource log = null; private static readonly Dictionary<(string Food, bool Blocked), double> decisions = new Dictionary<(string, bool), double>(); private Harmony? harmony; private static int cachedFrame = -1; private static readonly List<FeedingAnimal> animals = new List<FeedingAnimal>(); 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_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown log = ((BaseUnityPlugin)this).Logger; protectionEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Leave edible animal feed on the ground near tamed animals during automatic pickup. Manual pickup is unaffected."); radius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Protection Radius", 25f, new ConfigDescription("Maximum distance in metres from the dropped item to the nearest point on a living tamed animal's body collider (or its origin if no active collider is available). 0 disables protection. Includes animals that are already fed. Uses full 3D distance, without a line-of-sight requirement.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f), Array.Empty<object>())); diagnosticLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "Log Pickup Decisions", false, "Log automatic pickup decisions and the nearest diet-matching creature's distance and tame state. Useful for testing pickup conflicts; messages for the same food and outcome are limited to once every five seconds."); try { harmony = new Harmony("com.ziluck.valheim.animalfeedguard"); harmony.PatchAll(typeof(Plugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Animal Feed Guard ready. Protection radius: {radius.Value}m."); } catch (Exception arg) { ((BaseUnityPlugin)this).Logger.LogError((object)$"Animal Feed Guard could not patch automatic pickup. Feed protection is NOT active: {arg}"); Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } } private void OnDestroy() { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } animals.Clear(); cachedFrame = -1; decisions.Clear(); } internal static bool CanAutoPickup(ItemDrop item) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_012c: 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) if (!Object.op_Implicit((Object)(object)item) || !item.m_autoPickup) { return false; } if (!protectionEnabled.Value || radius.Value <= 0f) { return true; } string text = item.m_itemData?.m_shared?.m_name ?? string.Empty; if (text.Length == 0) { return true; } RefreshAnimals(); Vector3 position = ((Component)item).transform.position; string text2 = null; float num = float.PositiveInfinity; bool value = diagnosticLogging.Value; foreach (FeedingAnimal animal in animals) { Character character = animal.Character; MonsterAI aI = animal.AI; if (!Object.op_Implicit((Object)(object)character) || !Object.op_Implicit((Object)(object)aI) || aI.m_consumeItems == null) { continue; } bool flag = !character.IsDead(); bool flag2 = IsTamed(character); if (!value && (!flag || !flag2)) { continue; } float num2 = DistanceSquared(character, position); if (FeedRules.Protects(flag, flag2, num2, radius.Value, text, FoodNames(aI.m_consumeItems))) { if (value) { Report(text, blocked: true, Describe(character, num2, position, flag, flag2)); } return false; } if (value && num2 < num && FeedRules.HasFood(text, FoodNames(aI.m_consumeItems))) { num = num2; text2 = Describe(character, num2, position, flag, flag2); } } if (value) { Report(text, blocked: false, text2 ?? "no loaded creature has this food in its diet"); } return true; } internal static IEnumerable<string?> FoodNames(List<ItemDrop> foods) { foreach (ItemDrop food in foods) { if (Object.op_Implicit((Object)(object)food)) { yield return food.m_itemData?.m_shared?.m_name; } } } internal static bool IsTamed(Character character) { ZNetView component = ((Component)character).GetComponent<ZNetView>(); if (!Object.op_Implicit((Object)(object)component) || !component.IsValid()) { return character.IsTamed(); } return component.GetZDO().GetBool(ZDOVars.s_tamed, character.IsTamed()); } internal static float DistanceSquared(Character character, Vector3 itemPosition) { //IL_002a: 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) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: 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_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) Collider collider = (Collider)(object)character.GetCollider(); Vector3 val = ((Object.op_Implicit((Object)(object)collider) && collider.enabled && ((Component)collider).gameObject.activeInHierarchy) ? collider.ClosestPoint(itemPosition) : ((Component)character).transform.position); Vector3 val2 = itemPosition - val; return ((Vector3)(ref val2)).sqrMagnitude; } private static string Describe(Character character, float squaredDistance, Vector3 itemPosition, bool alive, bool tamed) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) return $"{Utils.GetPrefabName(((Component)character).gameObject)}: body distance {Math.Sqrt(squaredDistance):F2}m, origin distance {Vector3.Distance(itemPosition, ((Component)character).transform.position):F2}m, alive={alive}, tamed={tamed}"; } private static void Report(string food, bool blocked, string detail) { double timeAsDouble = Time.timeAsDouble; (string, bool) key = (food, blocked); if (!decisions.TryGetValue(key, out var value) || !(timeAsDouble - value < 5.0)) { decisions[key] = timeAsDouble; log.LogInfo((object)string.Format("Automatic pickup {0} for {1}; protection radius {2:F2}m; {3}.", blocked ? "blocked" : "allowed", food, radius.Value, detail)); } } private static void RefreshAnimals() { if (cachedFrame == Time.frameCount) { return; } cachedFrame = Time.frameCount; animals.Clear(); foreach (Character allCharacter in Character.GetAllCharacters()) { if (Object.op_Implicit((Object)(object)allCharacter)) { MonsterAI component = ((Component)allCharacter).GetComponent<MonsterAI>(); if (Object.op_Implicit((Object)(object)component)) { animals.Add(new FeedingAnimal(allCharacter, component)); } } } } } }