Decompiled source of HungerPangsPlus v2.0.3

HungerPangsPlus.dll

Decompiled 19 hours 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.InteropServices;
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: AssemblyTitle("HungerPangsPlus")]
[assembly: AssemblyProduct("HungerPangsPlus")]
[assembly: AssemblyCompany("Schachio")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("2.0.3.0")]
[assembly: AssemblyInformationalVersion("2.0.3")]
[assembly: ComVisible(false)]
[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.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Schachio.HungerPangsPlus
{
	[BepInPlugin("schachio.hungerpangsplus", "Hunger Pangs Plus", "2.0.3")]
	public sealed class HungerPangsPlusPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "schachio.hungerpangsplus";

		public const string PluginName = "Hunger Pangs Plus";

		public const string PluginVersion = "2.0.3";

		private ConfigEntry<bool> _enabled;

		private ConfigEntry<KeyboardShortcut> _toggleShortcut;

		private ConfigEntry<int> _maximumFoodSlots;

		private ConfigEntry<bool> _autoEat;

		private ConfigEntry<float> _eatInterval;

		private ConfigEntry<bool> _worldHarvest;

		private ConfigEntry<float> _harvestRadius;

		private ConfigEntry<float> _harvestInterval;

		private ConfigEntry<bool> _clickFoodSlots;

		private ConfigEntry<bool> _returnRemovedFood;

		private ConfigEntry<float> _manualPause;

		internal static ConfigEntry<bool> SuppressAutomationMessages;

		internal static bool AutomationAction;

		private Harmony _harmony;

		private float _nextEat;

		private float _nextHarvest;

		private float _pausedUntil;

		private int _lastFoodCount = -1;

		private FieldInfo _foodIconsField;

		private MethodInfo _updateFoodMethod;

		private void Awake()
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Expected O, but got Unknown
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Expected O, but got Unknown
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Expected O, but got Unknown
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Expected O, but got Unknown
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0204: Expected O, but got Unknown
			//IL_0289: Unknown result type (might be due to invalid IL or missing references)
			//IL_0293: Expected O, but got Unknown
			_enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master switch for all Hunger Pangs Plus automation.");
			_toggleShortcut = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "ToggleShortcut", new KeyboardShortcut((KeyCode)284, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Configurable shortcut for enabling or disabling all automation. Default: Left Alt + F3.");
			_maximumFoodSlots = ((BaseUnityPlugin)this).Config.Bind<int>("Food slots", "MaximumFoodSlots", 10, new ConfigDescription("Maximum number of detected food slots to fill. Valid range: 3-10.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(3, 10), Array.Empty<object>()));
			_autoEat = ((BaseUnityPlugin)this).Config.Bind<bool>("Auto eat", "Enabled", true, "Automatically consume suitable food from the player inventory when a detected food slot is available or refreshable.");
			_eatInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Auto eat", "CheckIntervalSeconds", 0.75f, new ConfigDescription("How often the inventory is checked for food.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 10f), Array.Empty<object>()));
			_worldHarvest = ((BaseUnityPlugin)this).Config.Bind<bool>("World food harvest", "Enabled", true, "Automatically harvest ready bushes and plants when their resulting item can be eaten directly.");
			_harvestRadius = ((BaseUnityPlugin)this).Config.Bind<float>("World food harvest", "Radius", 3.5f, new ConfigDescription("Maximum distance for automatic edible plant and berry harvesting.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 15f), Array.Empty<object>()));
			_harvestInterval = ((BaseUnityPlugin)this).Config.Bind<float>("World food harvest", "CheckIntervalSeconds", 0.5f, new ConfigDescription("How often nearby edible plants and berry bushes are checked.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 10f), Array.Empty<object>()));
			_clickFoodSlots = ((BaseUnityPlugin)this).Config.Bind<bool>("Manual selection", "ClickableFoodSlots", true, "Allow left or right click on an active food icon to remove that food.");
			_returnRemovedFood = ((BaseUnityPlugin)this).Config.Bind<bool>("Manual selection", "ReturnRemovedFoodToInventory", true, "Return one removed active food item to the inventory when possible.");
			_manualPause = ((BaseUnityPlugin)this).Config.Bind<float>("Manual selection", "PauseSeconds", 10f, new ConfigDescription("Pause auto-eating after manual food removal, giving time for a manual choice.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 60f), Array.Empty<object>()));
			SuppressAutomationMessages = ((BaseUnityPlugin)this).Config.Bind<bool>("Notifications", "SuppressAutomationMessages", true, "Suppress all on-screen Character messages caused by Hunger Pangs Plus automatic actions.");
			_foodIconsField = typeof(Hud).GetField("m_foodIcons", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			_updateFoodMethod = typeof(Player).GetMethod("UpdateFood", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[2]
			{
				typeof(float),
				typeof(bool)
			}, null);
			_harmony = new Harmony("schachio.hungerpangsplus");
			_harmony.PatchAll(typeof(HungerPangsPlusPlugin).Assembly);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Hunger Pangs Plus 2.0.3 loaded");
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void Update()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead())
			{
				return;
			}
			KeyboardShortcut value = _toggleShortcut.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				_enabled.Value = !_enabled.Value;
				((BaseUnityPlugin)this).Config.Save();
			}
			TrackManualFoodRemoval(localPlayer);
			HandleFoodIconClick(localPlayer);
			if (_enabled.Value)
			{
				float time = Time.time;
				if (_worldHarvest.Value && time >= _nextHarvest)
				{
					_nextHarvest = time + Mathf.Max(0.1f, _harvestInterval.Value);
					TryHarvestWorldFood(localPlayer);
				}
				if (_autoEat.Value && time >= _nextEat && time >= _pausedUntil)
				{
					_nextEat = time + Mathf.Max(0.25f, _eatInterval.Value);
					TryAutoEat(localPlayer);
				}
			}
		}

		private void TrackManualFoodRemoval(Player player)
		{
			int num = player.GetFoods()?.Count ?? 0;
			if (_lastFoodCount >= 0 && num < _lastFoodCount && Time.time >= _pausedUntil)
			{
				PauseAutoEat();
			}
			_lastFoodCount = num;
		}

		private void PauseAutoEat()
		{
			_pausedUntil = Time.time + Mathf.Max(0f, _manualPause.Value);
			_nextEat = _pausedUntil;
		}

		private void TryHarvestWorldFood(Player player)
		{
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (inventory == null)
			{
				return;
			}
			float num = Mathf.Max(0.5f, _harvestRadius.Value);
			foreach (Pickable item in from p in Object.FindObjectsOfType<Pickable>()
				where (Object)(object)p != (Object)null && (Object)(object)((Component)p).gameObject != (Object)null
				orderby Vector3.Distance(((Component)player).transform.position, ((Component)p).transform.position)
				select p)
			{
				if (Vector3.Distance(((Component)player).transform.position, ((Component)item).transform.position) > num)
				{
					break;
				}
				if (!item.CanBePicked() || (Object)(object)item.m_itemPrefab == (Object)null)
				{
					continue;
				}
				ItemDrop component = item.m_itemPrefab.GetComponent<ItemDrop>();
				if ((Object)(object)component == (Object)null || !IsFood(component.m_itemData) || !inventory.CanAddItem(item.m_itemPrefab, 1))
				{
					continue;
				}
				try
				{
					AutomationAction = true;
					item.Interact((Humanoid)(object)player, false, false);
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogDebug((object)("World food harvest skipped: " + ex.Message));
				}
				finally
				{
					AutomationAction = false;
				}
			}
		}

		private void TryAutoEat(Player player)
		{
			Inventory inventory = ((Humanoid)player).GetInventory();
			List<Food> active = player.GetFoods();
			if (inventory == null || active == null)
			{
				return;
			}
			int capacity = DetectFoodSlotCapacity();
			foreach (ItemData item in (from item in inventory.GetAllItems()
				where CanEatWithoutRejectedAttempt(active, item, capacity)
				select item).OrderByDescending(FoodScore).ToList())
			{
				try
				{
					AutomationAction = true;
					if (((Humanoid)player).ConsumeItem(inventory, item, false))
					{
						break;
					}
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogDebug((object)("Auto-eat skipped item: " + ex.Message));
				}
				finally
				{
					AutomationAction = false;
				}
			}
		}

		private int DetectFoodSlotCapacity()
		{
			int num = Mathf.Clamp(_maximumFoodSlots.Value, 3, 10);
			int val = 3;
			try
			{
				Hud instance = Hud.instance;
				object obj = (((Object)(object)instance != (Object)null && _foodIconsField != null) ? _foodIconsField.GetValue(instance) : null);
				if (obj is Array array)
				{
					val = array.Length;
				}
				else if (obj is ICollection collection)
				{
					val = collection.Count;
				}
			}
			catch
			{
				val = 3;
			}
			val = Math.Max(val, DetectExternalFoodSlotCapacity());
			return Mathf.Clamp(val, 3, num);
		}

		private static int DetectExternalFoodSlotCapacity()
		{
			int num = 3;
			try
			{
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				foreach (Assembly assembly in assemblies)
				{
					string text = assembly.GetName().Name ?? string.Empty;
					if (text.IndexOf("foodslot", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("foodslots", StringComparison.OrdinalIgnoreCase) < 0)
					{
						continue;
					}
					Type[] array;
					try
					{
						array = assembly.GetTypes();
					}
					catch (ReflectionTypeLoadException ex)
					{
						array = ex.Types.Where((Type t) => t != null).ToArray();
					}
					Type[] array2 = array;
					for (int num2 = 0; num2 < array2.Length; num2++)
					{
						FieldInfo[] fields = array2[num2].GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
						foreach (FieldInfo fieldInfo in fields)
						{
							if (fieldInfo.Name.IndexOf("MaxFood", StringComparison.OrdinalIgnoreCase) >= 0 || fieldInfo.Name.IndexOf("FoodSlotCount", StringComparison.OrdinalIgnoreCase) >= 0)
							{
								num = Math.Max(num, ReadConfiguredInteger(fieldInfo.GetValue(null)));
							}
						}
					}
				}
			}
			catch
			{
			}
			return Mathf.Clamp(num, 3, 10);
		}

		private static int ReadConfiguredInteger(object value)
		{
			if (value == null)
			{
				return 0;
			}
			try
			{
				if (value is int result)
				{
					return result;
				}
				object obj = value.GetType().GetProperty("Value", BindingFlags.Instance | BindingFlags.Public)?.GetValue(value, null);
				if (obj != null)
				{
					return Convert.ToInt32(obj);
				}
				obj = value.GetType().GetProperty("BoxedValue", BindingFlags.Instance | BindingFlags.Public)?.GetValue(value, null);
				return (obj != null) ? Convert.ToInt32(obj) : 0;
			}
			catch
			{
				return 0;
			}
		}

		private static bool CanEatWithoutRejectedAttempt(List<Food> active, ItemData item, int capacity)
		{
			if (!IsFood(item) || item.m_shared == null)
			{
				return false;
			}
			Food val = ((IEnumerable<Food>)active).FirstOrDefault((Func<Food, bool>)((Food f) => f != null && f.m_item != null && f.m_item.m_shared != null && f.m_item.m_shared.m_name == item.m_shared.m_name));
			if (val != null)
			{
				return val.CanEatAgain();
			}
			return active.Count < capacity;
		}

		private static bool IsFood(ItemData item)
		{
			if (item != null && item.m_shared != null)
			{
				if (!(item.m_shared.m_food > 0f) && !(item.m_shared.m_foodStamina > 0f))
				{
					return item.m_shared.m_foodEitr > 0f;
				}
				return true;
			}
			return false;
		}

		private static float FoodScore(ItemData item)
		{
			if (!IsFood(item))
			{
				return 0f;
			}
			return item.m_shared.m_food + item.m_shared.m_foodStamina + item.m_shared.m_foodEitr;
		}

		private void HandleFoodIconClick(Player player)
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			if (!_clickFoodSlots.Value || (!Input.GetMouseButtonDown(0) && !Input.GetMouseButtonDown(1)))
			{
				return;
			}
			Hud instance = Hud.instance;
			if ((Object)(object)instance == (Object)null || _foodIconsField == null)
			{
				return;
			}
			try
			{
				if (!(_foodIconsField.GetValue(instance) is IEnumerable enumerable))
				{
					return;
				}
				Vector2 val = Vector2.op_Implicit(Input.mousePosition);
				int num = 0;
				foreach (object item in enumerable)
				{
					Component val2 = (Component)((item is Component) ? item : null);
					if ((Object)(object)val2 != (Object)null && val2.gameObject.activeInHierarchy)
					{
						Transform transform = val2.transform;
						RectTransform val3 = (RectTransform)(object)((transform is RectTransform) ? transform : null);
						if (val3 != null && RectTransformUtility.RectangleContainsScreenPoint(val3, val, (Camera)null))
						{
							RemoveActiveFood(player, num);
							break;
						}
					}
					num++;
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogDebug((object)("Food icon click skipped: " + ex.Message));
			}
		}

		private void RemoveActiveFood(Player player, int index)
		{
			List<Food> foods = player.GetFoods();
			if (foods == null || index < 0 || index >= foods.Count)
			{
				return;
			}
			Food val = foods[index];
			if (val == null || val.m_item == null)
			{
				return;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (_returnRemovedFood.Value && inventory != null)
			{
				ItemData val2 = val.m_item.Clone();
				val2.m_stack = 1;
				if (!inventory.CanAddItem(val2, 1) || !inventory.AddItem(val2))
				{
					return;
				}
			}
			foods.RemoveAt(index);
			_updateFoodMethod?.Invoke(player, new object[2] { 0f, true });
			PauseAutoEat();
			_lastFoodCount = foods.Count;
		}
	}
	[HarmonyPatch(typeof(Character), "Message")]
	internal static class AutomationCharacterMessagePatch
	{
		private static bool Prefix(object[] __args)
		{
			return !AutomationMessageFilter.ShouldSuppress(__args);
		}
	}
	[HarmonyPatch(typeof(MessageHud), "ShowMessage")]
	internal static class AutomationHudMessagePatch
	{
		private static bool Prefix(object[] __args)
		{
			return !AutomationMessageFilter.ShouldSuppress(__args);
		}
	}
	internal static class AutomationMessageFilter
	{
		internal static bool ShouldSuppress(object[] arguments)
		{
			if (HungerPangsPlusPlugin.SuppressAutomationMessages == null || !HungerPangsPlusPlugin.SuppressAutomationMessages.Value)
			{
				return false;
			}
			if (HungerPangsPlusPlugin.AutomationAction)
			{
				return true;
			}
			if (arguments == null)
			{
				return false;
			}
			for (int i = 0; i < arguments.Length; i++)
			{
				string text = arguments[i] as string;
				if (!string.IsNullOrEmpty(text))
				{
					string text2 = text.ToLowerInvariant();
					if (text2.StartsWith("+") && (text2.Contains("gesund") || text2.Contains("health") || text2.Contains("ausdauer") || text2.Contains("stamina") || text2.Contains("eitr")))
					{
						return true;
					}
					if (text2.Contains("$msg_canteat") || text2.Contains("full stomach") || text2.Contains("magen ist voll"))
					{
						return true;
					}
				}
			}
			return false;
		}
	}
}