using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using AutoFeed.Core; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Utils; 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("Narolith.AutoFeed")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Valheim mod to auto feed animals from a nearby chest")] [assembly: AssemblyFileVersion("1.0.2.0")] [assembly: AssemblyInformationalVersion("1.0.2+134a89fcff9edb0f1fc7189cd2c11af19bd815c2")] [assembly: AssemblyProduct("Narolith.AutoFeed")] [assembly: AssemblyTitle("Narolith.AutoFeed")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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 AutoFeed { public static class ContainerExtensions { public static bool ContainersContainItemFromList(this List containers, Dictionary consumableMap, out Container? targetContainer, out ItemData? targetItem) { return FindItemInContainers(containers, consumableMap, out targetContainer, out targetItem); } private static bool FindItemInContainers(List containers, Dictionary consumableMap, out Container? targetContainer, out ItemData? targetItem) { foreach (Container container in containers) { if (TryFindMatchingItem(container.GetInventory().GetAllItems(), consumableMap, out targetItem)) { targetContainer = container; return true; } } targetContainer = null; targetItem = null; return false; } private static bool TryFindMatchingItem(List items, Dictionary consumableMap, out ItemData? targetItem) { foreach (ItemData item in items) { if (consumableMap.TryGetValue(item.m_shared.m_name, out ItemData value)) { targetItem = value; return true; } } targetItem = null; return false; } } public static class MonsterAIExtensions { public static void FeedMonsterWithThrottling(this MonsterAI __instance, Tameable ___m_tamable, Character ___m_character, Container container, ItemData item) { FeedAnimal(__instance, ___m_tamable, ___m_character, container, item); Plugin.LastFeedTimes[((Object)___m_character).GetInstanceID()] = Time.time; } private static void FeedAnimal(MonsterAI monsterAI, Tameable tamable, Character character, Container container, ItemData item) { if (tamable != null && monsterAI != null && tamable.IsHungry()) { monsterAI.ConsumeItem(character); ZNetView component = ((Component)container).GetComponent(); if (component != null && component.IsValid()) { container.GetInventory().RemoveItem(item.m_shared.m_name, 1, -1, true); Traverse.Create((object)container.GetInventory()).Method("Changed", Array.Empty()).GetValue(); Traverse.Create((object)container).Method("Save", Array.Empty()).GetValue(); } } } public static void ConsumeItem(this MonsterAI monsterAI, Character character) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) monsterAI.m_onConsumedItem?.Invoke(null); Character obj = ((character is Humanoid) ? character : null); if (obj != null) { ((Humanoid)obj).m_consumeItemEffects.Create(((Component)character).transform.position, Quaternion.identity, (Transform)null, 1f, -1); } ZSyncAnimation value = Traverse.Create((object)monsterAI).Field("m_animator").GetValue(); if (value != null) { value.SetTrigger("consume"); } } } public static class Vector3Extensions { public static List GetContainersInRange(this Vector3 center, float radiusRange, int animalId, float currentTime) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: 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_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: 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.ContainerCache.TryGetValue(animalId, out (float, List) value) && FeedingLogic.IsCacheValid(value.Item1, currentTime, Plugin.CacheTtl.Value)) { value.Item2.RemoveAll((Container c) => (Object)(object)c == (Object)null); return value.Item2; } float num = radiusRange * radiusRange; List list = new List(); foreach (Piece s_allPiece in Piece.s_allPieces) { if ((Object)(object)s_allPiece == (Object)null) { continue; } Container component = ((Component)s_allPiece).GetComponent(); if (!((Object)(object)component == (Object)null)) { Vector3 val = ((Component)s_allPiece).transform.position - center; if (((Vector3)(ref val)).sqrMagnitude <= num && IsValidZNetView(((Component)component).GetComponent()) && IsEligibleContainer(component)) { list.Add(component); } } } List list2 = (from x in list.Select(delegate(Container c) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) Vector3 val2 = ((Component)c).transform.position - center; return (c, ((Vector3)(ref val2)).sqrMagnitude); }) orderby x.sqrDist select x.container).ToList(); Plugin.ContainerCache[animalId] = (currentTime, list2); return list2; } private static bool IsValidZNetView(ZNetView? zNetView) { if (zNetView != null) { return zNetView.IsValid(); } return false; } private static bool IsEligibleContainer(Container container) { Inventory inventory = container.GetInventory(); return FeedingLogic.IsEligibleContainer(((Object)container).name, (inventory != null) ? inventory.NrOfItems() : 0, Plugin.ChestPrefix.Value); } } [BepInPlugin("Narolith.AutoFeed", "Narolith.AutoFeed", "1.0.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { [HarmonyPatch(typeof(MonsterAI), "UpdateConsumeItem")] private static class UpdateConsumeItemPatch { private static void Postfix(MonsterAI __instance, Character ___m_character, Tameable ___m_tamable, List ___m_consumeItems, bool __result) { //IL_00eb: Unknown result type (might be due to invalid IL or missing references) Character ___m_character2 = ___m_character; List ___m_consumeItems2 = ___m_consumeItems; Tameable ___m_tamable2 = ___m_tamable; if (Player.m_localPlayer == null || !ModEnabled() || !HasCharacterData() || !IsTamedAndHungry() || !HasValidFoodTypes() || HasFoundFood()) { return; } int instanceID = ((Object)___m_character2).GetInstanceID(); if (!FeedingLogic.ShouldFeed(instanceID, Time.time, LastFeedTimes, 5f)) { return; } Dictionary dictionary = new Dictionary(); foreach (ItemDrop item in ___m_consumeItems2) { string name = item.m_itemData.m_shared.m_name; if (!dictionary.ContainsKey(name)) { dictionary[name] = item.m_itemData; } } if (((Component)___m_character2).gameObject.transform.position.GetContainersInRange(ContainerRange.Value, instanceID, Time.time).ContainersContainItemFromList(dictionary, out Container targetContainer, out ItemData targetItem)) { __instance.FeedMonsterWithThrottling(___m_tamable2, ___m_character2, targetContainer, targetItem); } bool HasCharacterData() { return ___m_character2 != null; } bool HasFoundFood() { return __result; } bool HasValidFoodTypes() { if (___m_consumeItems2 != null) { return ___m_consumeItems2.Count > 0; } return false; } bool IsTamedAndHungry() { if (___m_tamable2 != null) { return ___m_tamable2.IsHungry(); } return false; } static bool ModEnabled() { return Plugin.ModEnabled.Value; } } } internal static ManualLogSource Log = null; private static Harmony? _harmony; public static readonly Dictionary LastFeedTimes = new Dictionary(); internal static readonly Dictionary containers)> ContainerCache = new Dictionary)>(); public static ConfigEntry ContainerRange = null; public static ConfigEntry ModEnabled = null; public static ConfigEntry ChestPrefix = null; public static ConfigEntry CacheTtl = null; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"AutoFeed v1.0.2 loading..."); ContainerRange = ((BaseUnityPlugin)this).Config.Bind("General", "Container Range", 10f, "The radius in which a tamed creature will look for containers to feed from."); ModEnabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Whether the mod is enabled."); ChestPrefix = ((BaseUnityPlugin)this).Config.Bind("General", "Chest Prefix", "piece_chest", "Name prefix for containers eligible for auto-feeding. Leave empty to allow all containers."); CacheTtl = ((BaseUnityPlugin)this).Config.Bind("General", "Container Cache TTL", 5f, "Seconds before the nearby-container list is refreshed for each animal."); _harmony = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null); Log.LogInfo((object)"AutoFeed loaded."); } } public static class PluginSettings { public const float FeedInterval = 5f; } public static class PluginInfo { public const string PLUGIN_GUID = "Narolith.AutoFeed"; public const string PLUGIN_NAME = "Narolith.AutoFeed"; public const string PLUGIN_VERSION = "1.0.2"; } } namespace AutoFeed.Core { public static class FeedingLogic { public static string? FindConsumableInInventory(IEnumerable inventoryItemNames, HashSet consumableNames) { foreach (string inventoryItemName in inventoryItemNames) { if (consumableNames.Contains(inventoryItemName)) { return inventoryItemName; } } return null; } public static bool ShouldFeed(int animalId, float currentTime, IReadOnlyDictionary lastFeedTimes, float interval) { if (!lastFeedTimes.TryGetValue(animalId, out var value)) { return true; } return currentTime - value >= interval; } public static bool IsEligibleContainer(string containerName, int itemCount, string prefix) { if (!string.IsNullOrEmpty(prefix) && !containerName.StartsWith(prefix)) { return false; } return itemCount > 0; } public static bool IsCacheValid(float cachedTimestamp, float currentTime, float ttl) { return currentTime - cachedTimestamp < ttl; } } }