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 Auto Food v1.0.3
plugins/AutoFood.dll
Decompiled a week agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("BenjSammy")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Remember each chosen food and renew it before expiry through a serial, effect-confirmed retry queue.")] [assembly: AssemblyFileVersion("1.0.3.0")] [assembly: AssemblyInformationalVersion("1.0.3")] [assembly: AssemblyProduct("Auto Food by BenjSammy")] [assembly: AssemblyTitle("AutoFood")] [assembly: AssemblyVersion("1.0.3.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 BenjSammy.AutoFood { [BepInPlugin("benjsammy.autofood", "Auto Food by BenjSammy", "1.0.3")] public sealed class AutoFoodPlugin : BaseUnityPlugin { public const string PluginGuid = "benjsammy.autofood"; public const string PluginName = "Auto Food by BenjSammy"; public const string PluginVersion = "1.0.3"; private Harmony _harmony; private FoodEngine _engine; private ConfigEntry<bool> _enabled; private ConfigEntry<float> _lead; private ConfigEntry<float> _retry; private float _nextError; private void Awake() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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 //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown _enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Renouveler le même aliment de l'inventaire avant la fin de son effet."); _lead = ((BaseUnityPlugin)this).Config.Bind<float>("General", "SecondsBeforeExpiry", 30f, new ConfigDescription("Commencer à remanger lorsqu'il reste ce nombre de secondes d'effet.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 600f), Array.Empty<object>())); _retry = ((BaseUnityPlugin)this).Config.Bind<float>("General", "RetrySeconds", 1f, new ConfigDescription("Délai entre les nouveaux essais d'un repas bloqué ou les contrôles d'un stock vide. Une consommation à la fois, espacée d'au moins une seconde.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 60f), Array.Empty<object>())); ConfigEntry<int> val = ((BaseUnityPlugin)this).Config.Bind<int>("Internal", "SettingsVersion", 0, "Version interne de migration des réglages."); if (val.Value < 2) { val.Value = 2; } _harmony = new Harmony("benjsammy.autofood"); try { Type player = FoodApi.RequireType("Player"); Type nestedType = FoodApi.RequireType("ItemDrop").GetNestedType("ItemData", BindingFlags.Public | BindingFlags.NonPublic); if (nestedType == null) { throw new TypeLoadException("ItemDrop.ItemData"); } FoodApi api = new FoodApi(player, FoodApi.RequireType("Inventory"), nestedType); _engine = new FoodEngine(api, () => Time.time, () => _lead.Value, () => _enabled.Value, () => _retry.Value); FoodPatches.Install(_harmony, player, _engine, Report); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Auto Food 1.0.3 by BenjSammy active. Remembers each food, renews before expiry, confirms effects and processes meals one at a time. Client-side installation."); } catch (Exception ex) { _harmony.UnpatchSelf(); FoodPatches.Reset(); _engine = null; ((BaseUnityPlugin)this).Logger.LogError((object)("Auto Food could not start: incompatible Valheim API. " + ex)); } } private void Update() { if (_engine == null || Time.deltaTime <= 0f) { return; } try { _engine.Tick(); } catch (Exception exception) { _engine.Reset(); Report(exception); } } private void Report(Exception exception) { if (!(Time.realtimeSinceStartup < _nextError)) { _nextError = Time.realtimeSinceStartup + 15f; ((BaseUnityPlugin)this).Logger.LogWarning((object)("Auto Food: " + exception)); } } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } FoodPatches.Reset(); _engine?.Reset(); } } internal sealed class FoodApi : IFoodApi { private const BindingFlags All = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private readonly MemberInfo _local; private readonly MethodInfo _foods; private readonly FieldInfo _foodItem; private readonly FieldInfo _time; private readonly FieldInfo _shared; private readonly FieldInfo _sharedName; private readonly FieldInfo _duration; private readonly FieldInfo _prefab; private readonly PropertyInfo _prefabName; private readonly FieldInfo _stack; private readonly MethodInfo _inventory; private readonly MethodInfo _allItems; private readonly MethodInfo _canEat; private readonly MethodInfo _consume; private readonly MethodInfo _isDead; private readonly MethodInfo _seMan; private readonly MethodInfo _statusEffects; private readonly List<MethodInfo> _busy = new List<MethodInfo>(); public object LocalPlayer { get { FieldInfo fieldInfo = _local as FieldInfo; if (!(fieldInfo != null)) { return ((PropertyInfo)_local).GetValue(null, null); } return fieldInfo.GetValue(null); } } internal FoodApi(Type player, Type inventory, Type item) { _local = (MemberInfo)(((object)player.GetField("m_localPlayer", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) ?? ((object)player.GetProperty("m_localPlayer", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))); if (_local == null) { throw new MissingMemberException("Player.m_localPlayer"); } Type nestedType = player.GetNestedType("Food", BindingFlags.Public | BindingFlags.NonPublic); if (nestedType == null) { throw new TypeLoadException("Player.Food"); } _foods = Method(player, "GetFoods", Type.EmptyTypes); _foodItem = Field(nestedType, "m_item", item); _time = Field(nestedType, "m_time", typeof(float)); _shared = Field(item, "m_shared"); _sharedName = Field(_shared.FieldType, "m_name", typeof(string)); _duration = _shared.FieldType.GetField("m_foodBurnTime", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (_duration != null && _duration.FieldType != typeof(float)) { _duration = null; } _prefab = item.GetField("m_dropPrefab", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _prefabName = _prefab?.FieldType.GetProperty("name", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _stack = Field(item, "m_stack", typeof(int)); _inventory = Method(player, "GetInventory", Type.EmptyTypes); _allItems = Method(inventory, "GetAllItems", Type.EmptyTypes); _canEat = Method(player, "CanEat", new Type[2] { item, typeof(bool) }, typeof(bool)); _consume = Method(player, "ConsumeItem", new Type[3] { inventory, item, typeof(bool) }, typeof(bool)); _isDead = Method(player, "IsDead", Type.EmptyTypes, typeof(bool)); _seMan = player.GetMethod("GetSEMan", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); _statusEffects = _seMan?.ReturnType.GetMethod("GetStatusEffects", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); string[] array = new string[8] { "IsTeleporting", "IsSleeping", "InCutscene", "IsRunning", "InAttack", "InDodge", "IsStaggering", "InMinorAction" }; foreach (string name in array) { MethodInfo method = player.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method != null && method.ReturnType == typeof(bool)) { _busy.Add(method); } } } public bool IsAlive(object player) { if (player != null) { return !(bool)_isDead.Invoke(player, null); } return false; } public float Remaining(object token) { return (float)_time.GetValue(token); } public bool IsPurging(object player) { if (_seMan == null || _statusEffects == null) { return false; } object obj = _seMan.Invoke(player, null); IEnumerable enumerable = ((obj == null) ? null : (_statusEffects.Invoke(obj, null) as IEnumerable)); if (enumerable == null) { return false; } foreach (object item in enumerable) { Type type = item?.GetType(); while (type != null) { if (type.Name == "SE_Puke") { return true; } type = type.BaseType; } } return false; } public List<FoodState> Foods(object player) { List<FoodState> list = new List<FoodState>(); if (!(_foods.Invoke(player, null) is IEnumerable enumerable)) { return list; } foreach (object item in enumerable) { if (item != null) { object value = _foodItem.GetValue(item); FoodKind foodKind = Kind(value); if (foodKind != null) { object value2 = _shared.GetValue(value); float duration = ((_duration == null || value2 == null) ? 0f : ((float)_duration.GetValue(value2))); list.Add(new FoodState { Token = item, Kind = foodKind, Duration = duration }); } } } return list; } private FoodKind Kind(object item) { if (item == null) { return null; } object value = _shared.GetValue(item); if (value == null) { return null; } string text = (string)_sharedName.GetValue(value); object obj = _prefab?.GetValue(item); string text2 = ((obj == null || _prefabName == null) ? null : (_prefabName.GetValue(obj, null) as string)); if (string.IsNullOrEmpty(text2) && string.IsNullOrEmpty(text)) { return null; } return new FoodKind { Prefab = text2, SharedName = text }; } public ConsumeResult ConsumeSame(object player, FoodKind kind) { if (player != LocalPlayer || !IsAlive(player)) { return ConsumeResult.Blocked; } object obj = _inventory.Invoke(player, null); if (obj == null) { return ConsumeResult.Missing; } if (!(_allItems.Invoke(obj, null) is IEnumerable enumerable)) { return ConsumeResult.Missing; } object obj2 = null; foreach (object item in enumerable) { if (item != null && (int)_stack.GetValue(item) > 0 && kind.Matches(Kind(item))) { obj2 = item; break; } } if (obj2 == null) { return ConsumeResult.Missing; } foreach (MethodInfo item2 in _busy) { if ((bool)item2.Invoke(player, null)) { return ConsumeResult.Blocked; } } if (!(bool)_canEat.Invoke(player, new object[2] { obj2, false })) { return ConsumeResult.Blocked; } if (!(bool)_consume.Invoke(player, new object[3] { obj, obj2, true })) { return ConsumeResult.Blocked; } return ConsumeResult.Eaten; } internal static Type RequireType(string name) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { Type type = assemblies[i].GetType(name, throwOnError: false); if (type != null) { return type; } } throw new TypeLoadException("Valheim type not found: " + name); } private static FieldInfo Field(Type type, string name, Type expected = null) { FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field == null || (expected != null && field.FieldType != expected)) { throw new MissingFieldException(type.FullName, name); } return field; } private static MethodInfo Method(Type type, string name, Type[] parameters, Type expected = null) { MethodInfo method = type.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, parameters, null); if (method == null || (expected != null && method.ReturnType != expected)) { throw new MissingMethodException(type.FullName, name); } return method; } } internal sealed class FoodKind { internal string Prefab; internal string SharedName; internal string Key { get { if (string.IsNullOrEmpty(Prefab)) { return "n:" + SharedName; } return "p:" + Prefab; } } internal bool Matches(FoodKind other) { if (other == null) { return false; } if (!string.IsNullOrEmpty(Prefab) && !string.IsNullOrEmpty(other.Prefab)) { return string.Equals(Prefab, other.Prefab, StringComparison.Ordinal); } if (!string.IsNullOrEmpty(SharedName)) { return string.Equals(SharedName, other.SharedName, StringComparison.Ordinal); } return false; } } internal sealed class FoodState { internal object Token; internal FoodKind Kind; internal float Duration; } internal enum ConsumeResult { Eaten, Missing, Blocked } internal interface IFoodApi { object LocalPlayer { get; } bool IsAlive(object player); bool IsPurging(object player); List<FoodState> Foods(object player); float Remaining(object foodToken); ConsumeResult ConsumeSame(object player, FoodKind kind); } internal sealed class FoodEngine { private sealed class Meal { internal string Id; internal FoodKind Kind; internal float Duration; internal object LastToken; internal float LastTime; internal double NextTry; internal long Order; internal bool OutOfStock; } private sealed class Confirmation { internal Meal Meal; internal float BeforeRemaining; internal double Deadline; } private readonly IFoodApi _api; private readonly Func<double> _clock; private readonly Func<double> _lead; private readonly Func<bool> _enabled; private readonly Func<double> _retry; private readonly Dictionary<string, Meal> _meals = new Dictionary<string, Meal>(StringComparer.Ordinal); private List<FoodState> _lastActive = new List<FoodState>(); private object _owner; private Confirmation _confirmation; private double _nextAction; private long _order; private int _capacity = 3; private bool _automatic; internal FoodEngine(IFoodApi api, Func<double> clock, Func<double> secondsBeforeExpiry, Func<bool> enabled, Func<double> retry = null) { _api = api; _clock = clock; _lead = secondsBeforeExpiry; _enabled = enabled; _retry = retry ?? ((Func<double>)(() => 1.0)); } private bool Ready(object player) { object localPlayer = _api.LocalPlayer; if (localPlayer != _owner) { Reset(); _owner = localPlayer; } if (!_enabled() || localPlayer == null || !_api.IsAlive(localPlayer) || _api.IsPurging(localPlayer)) { Reset(); _owner = localPlayer; return false; } return player == localPlayer; } private List<FoodState> Active(object player) { return (from food in _api.Foods(player) where _api.Remaining(food.Token) > 0f select food).ToList(); } private FoodState Find(List<FoodState> foods, FoodKind kind) { return foods.FirstOrDefault((FoodState food) => kind.Matches(food.Kind)); } private Meal FindMeal(FoodKind kind) { return _meals.Values.FirstOrDefault((Meal meal) => kind.Matches(meal.Kind)); } private List<FoodState> Observe(object player) { List<FoodState> active = Active(player); foreach (FoodState item in _lastActive) { if (Find(active, item.Kind) == null && !(_api.Remaining(item.Token) <= 0f)) { Meal meal = FindMeal(item.Kind); if (meal != null && !_automatic && (_confirmation == null || _confirmation.Meal != meal)) { _meals.Remove(meal.Id); } } } _capacity = Math.Max(_capacity, active.Count); foreach (FoodState item2 in active) { float num = _api.Remaining(item2.Token); Meal meal2 = FindMeal(item2.Kind); if (meal2 == null) { meal2 = new Meal { Id = item2.Kind.Key, Kind = item2.Kind, Order = _order++, NextTry = 0.0 }; _meals[meal2.Id] = meal2; } else if (meal2.LastToken != item2.Token || num > meal2.LastTime + 0.01f) { meal2.OutOfStock = false; meal2.NextTry = 0.0; } meal2.Kind = item2.Kind; meal2.Duration = item2.Duration; meal2.LastToken = item2.Token; meal2.LastTime = num; } int num2 = _meals.Count - _capacity; if (num2 > 0) { Meal[] array = (from meal4 in _meals.Values where Find(active, meal4.Kind) == null orderby (!meal4.OutOfStock) ? 1 : 0, meal4.Order select meal4).Take(num2).ToArray(); foreach (Meal meal3 in array) { _meals.Remove(meal3.Id); } } _lastActive = active; return active; } private bool NeedsRenewal(Meal meal, List<FoodState> active) { FoodState foodState = Find(active, meal.Kind); if (foodState == null) { return true; } double num = Math.Max(0.0, _lead()); if (meal.Duration > 0f && !float.IsInfinity(meal.Duration)) { num = Math.Min(num, (double)meal.Duration * 0.5); } return (double)_api.Remaining(foodState.Token) <= num; } private bool Confirmed(Meal meal, float before, List<FoodState> active) { FoodState foodState = Find(active, meal.Kind); if (foodState != null) { return _api.Remaining(foodState.Token) > Math.Max(0f, before) + 0.01f; } return false; } internal FoodState[] BeforeFoodUpdate(object player, float dt, bool forced) { if (forced || dt <= 0f || !Ready(player)) { return null; } return Observe(player).ToArray(); } internal void AfterFoodUpdate(object player, FoodState[] before) { if (before != null && Ready(player)) { Observe(player); } } internal FoodState[] BeforeManualConsumption(object player) { if (!_automatic && Ready(player)) { return Observe(player).ToArray(); } return null; } internal void AfterManualConsumption(object player, FoodState[] before, bool success) { if (before == null || !success || !Ready(player)) { return; } List<FoodState> foods = Active(player); foreach (FoodState foodState in before) { if (Find(foods, foodState.Kind) == null) { Meal meal = FindMeal(foodState.Kind); if (meal != null) { _meals.Remove(meal.Id); } } } Observe(player); } internal void Tick() { object localPlayer = _api.LocalPlayer; if (!Ready(localPlayer)) { return; } double now = _clock(); List<FoodState> active = Observe(localPlayer); if (_confirmation != null) { Confirmation confirmation = _confirmation; if (!_meals.ContainsKey(confirmation.Meal.Id) || Confirmed(confirmation.Meal, confirmation.BeforeRemaining, active)) { confirmation.Meal.NextTry = 0.0; _confirmation = null; } else { if (now < confirmation.Deadline) { return; } confirmation.Meal.NextTry = now; _confirmation = null; } } if (now < _nextAction) { return; } Meal[] array = (from meal2 in _meals.Values where meal2.NextTry <= now && NeedsRenewal(meal2, active) orderby meal2.NextTry, (Find(active, meal2.Kind) != null) ? _api.Remaining(Find(active, meal2.Kind).Token) : (-1f), meal2.Order select meal2).ToArray(); foreach (Meal meal in array) { active = Observe(localPlayer); if (!_meals.ContainsKey(meal.Id)) { continue; } FoodState foodState = Find(active, meal.Kind); if (foodState == null && active.Count >= _capacity) { meal.NextTry = now + Math.Max(0.1, _retry()); continue; } float num2 = ((foodState == null) ? 0f : _api.Remaining(foodState.Token)); _automatic = true; ConsumeResult consumeResult; try { consumeResult = _api.ConsumeSame(localPlayer, meal.Kind); } finally { _automatic = false; } if (consumeResult == ConsumeResult.Eaten) { _confirmation = new Confirmation { Meal = meal, BeforeRemaining = num2, Deadline = now + 5.0 }; } active = Observe(localPlayer); if (Confirmed(meal, num2, active)) { meal.NextTry = 0.0; _confirmation = null; meal.OutOfStock = false; } else { switch (consumeResult) { case ConsumeResult.Missing: meal.OutOfStock = true; meal.NextTry = now + Math.Max(0.1, _retry()); continue; case ConsumeResult.Blocked: meal.NextTry = now + Math.Max(0.1, _retry()); break; } } _nextAction = now + 1.0; break; } } internal void CancelFor(object player) { if (player == _api.LocalPlayer) { Reset(); } } internal void Reset() { _owner = null; _meals.Clear(); _lastActive.Clear(); _confirmation = null; _nextAction = 0.0; _order = 0L; _capacity = 3; _automatic = false; } } internal static class FoodPatches { private static FoodEngine _engine; private static Action<Exception> _error; private static bool _reported; internal static void Install(Harmony harmony, Type player, FoodEngine engine, Action<Exception> error) { MethodInfo method = player.GetMethod("UpdateFood", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(float), typeof(bool) }, null); if (method == null || method.ReturnType != typeof(void)) { throw new MissingMethodException("Player.UpdateFood(float, bool)"); } _engine = engine; _error = error; _reported = false; harmony.Patch((MethodBase)method, Hook("Before"), Hook("After"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Type nestedType = FoodApi.RequireType("ItemDrop").GetNestedType("ItemData", BindingFlags.Public | BindingFlags.NonPublic); Type type = FoodApi.RequireType("Inventory"); MethodInfo method2 = player.GetMethod("ConsumeItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[3] { type, nestedType, typeof(bool) }, null); if (method2 == null || method2.ReturnType != typeof(bool)) { throw new MissingMethodException("Player.ConsumeItem(Inventory, ItemData, bool)"); } harmony.Patch((MethodBase)method2, Hook("BeforeConsume"), Hook("AfterConsume"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); string[] array = new string[2] { "ClearFood", "RemoveOneFood" }; foreach (string name in array) { MethodInfo method3 = player.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method3 != null) { harmony.Patch((MethodBase)method3, (HarmonyMethod)null, Hook("Clear"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static HarmonyMethod Hook(string name) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown return new HarmonyMethod(typeof(FoodPatches).GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic)); } private static void Before(object __instance, float __0, bool __1, out FoodState[] __state) { __state = null; try { __state = _engine?.BeforeFoodUpdate(__instance, __0, __1); } catch (Exception exception) { Report(exception); } } private static void After(object __instance, FoodState[] __state) { try { _engine?.AfterFoodUpdate(__instance, __state); } catch (Exception exception) { Report(exception); } } private static void Clear(object __instance) { try { _engine?.CancelFor(__instance); } catch (Exception exception) { Report(exception); } } private static void BeforeConsume(object __instance, out FoodState[] __state) { __state = null; try { __state = _engine?.BeforeManualConsumption(__instance); } catch (Exception exception) { Report(exception); } } private static void AfterConsume(object __instance, bool __result, FoodState[] __state) { try { _engine?.AfterManualConsumption(__instance, __state, __result); } catch (Exception exception) { Report(exception); } } private static void Report(Exception exception) { if (!_reported) { _reported = true; _error?.Invoke(exception); } } internal static void Reset() { _engine = null; _error = null; _reported = false; } } }