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 _enabled; private ConfigEntry _toggleShortcut; private ConfigEntry _maximumFoodSlots; private ConfigEntry _autoEat; private ConfigEntry _eatInterval; private ConfigEntry _worldHarvest; private ConfigEntry _harvestRadius; private ConfigEntry _harvestInterval; private ConfigEntry _clickFoodSlots; private ConfigEntry _returnRemovedFood; private ConfigEntry _manualPause; internal static ConfigEntry 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("General", "Enabled", true, "Master switch for all Hunger Pangs Plus automation."); _toggleShortcut = ((BaseUnityPlugin)this).Config.Bind("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("Food slots", "MaximumFoodSlots", 10, new ConfigDescription("Maximum number of detected food slots to fill. Valid range: 3-10.", (AcceptableValueBase)(object)new AcceptableValueRange(3, 10), Array.Empty())); _autoEat = ((BaseUnityPlugin)this).Config.Bind("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("Auto eat", "CheckIntervalSeconds", 0.75f, new ConfigDescription("How often the inventory is checked for food.", (AcceptableValueBase)(object)new AcceptableValueRange(0.25f, 10f), Array.Empty())); _worldHarvest = ((BaseUnityPlugin)this).Config.Bind("World food harvest", "Enabled", true, "Automatically harvest ready bushes and plants when their resulting item can be eaten directly."); _harvestRadius = ((BaseUnityPlugin)this).Config.Bind("World food harvest", "Radius", 3.5f, new ConfigDescription("Maximum distance for automatic edible plant and berry harvesting.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 15f), Array.Empty())); _harvestInterval = ((BaseUnityPlugin)this).Config.Bind("World food harvest", "CheckIntervalSeconds", 0.5f, new ConfigDescription("How often nearby edible plants and berry bushes are checked.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 10f), Array.Empty())); _clickFoodSlots = ((BaseUnityPlugin)this).Config.Bind("Manual selection", "ClickableFoodSlots", true, "Allow left or right click on an active food icon to remove that food."); _returnRemovedFood = ((BaseUnityPlugin)this).Config.Bind("Manual selection", "ReturnRemovedFoodToInventory", true, "Return one removed active food item to the inventory when possible."); _manualPause = ((BaseUnityPlugin)this).Config.Bind("Manual selection", "PauseSeconds", 10f, new ConfigDescription("Pause auto-eating after manual food removal, giving time for a manual choice.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 60f), Array.Empty())); SuppressAutomationMessages = ((BaseUnityPlugin)this).Config.Bind("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() 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(); 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 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 active, ItemData item, int capacity) { if (!IsFood(item) || item.m_shared == null) { return false; } Food val = ((IEnumerable)active).FirstOrDefault((Func)((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 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; } } }