Decompiled source of Auto Food v1.0.3

plugins/AutoFood.dll

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