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 HungerPangsPlus v2.0.3
HungerPangsPlus.dll
Decompiled 19 hours 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.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; } } }