Decompiled source of AnimalFeedGuard v2.0.3

BepInEx/plugins/AnimalFeedGuard/AnimalFeedGuard.dll

Decompiled a month ago
using 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));
					}
				}
			}
		}
	}
}