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 HearthPantry v1.0.4
plugins/HearthPantry.dll
Decompiled a week agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyCompany("blackhearthx")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Renews your manually chosen active meals, pauses food timers at the workbench or on a boat, and handles fight meads.")] [assembly: AssemblyFileVersion("1.0.4.0")] [assembly: AssemblyInformationalVersion("1.0.4+710d9fa2fcda5022c6fdd04e1168ca335ee15905")] [assembly: AssemblyProduct("HearthPantry")] [assembly: AssemblyTitle("HearthPantry")] [assembly: AssemblyVersion("1.0.4.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 HearthPantry { internal static class FoodManager { private const float WorkbenchRange = 20f; private static readonly HashSet<string> _expiryShown = new HashSet<string>(); private static readonly HashSet<string> _lowSupplyShown = new HashSet<string>(); private static readonly HashSet<string> _seenThisTick = new HashSet<string>(); private static bool _nearWorkbench; private static bool _onBoat; private static Player _trackedPlayer; private static bool _foodRateResolved; private static FieldRef<float> _foodRateRef; private static bool _tcResolved; private static MethodInfo _tcMultiplierGetter; private const string TimeControlGuid = "drummercraig.time_control"; public static bool NearWorkbench => _nearWorkbench; public static bool OnBoat => _onBoat; public static void Tick() { Player localPlayer = Player.m_localPlayer; if (localPlayer != _trackedPlayer) { _trackedPlayer = localPlayer; _expiryShown.Clear(); _lowSupplyShown.Clear(); } _nearWorkbench = PluginConfig.ModEnabled.Value && (Object)(object)localPlayer != (Object)null && PluginConfig.PauseNearWorkbench.Value && IsNearWorkbench(localPlayer); _onBoat = PluginConfig.ModEnabled.Value && (Object)(object)localPlayer != (Object)null && PluginConfig.PauseOnBoat.Value && ((Character)localPlayer).IsAttachedToShip(); if (PluginConfig.ModEnabled.Value && !((Object)(object)localPlayer == (Object)null) && CanAutomate(localPlayer) && (PluginConfig.AutoEat.Value || PluginConfig.ExpiryNotify.Value)) { CheckFoods(localPlayer); } } internal static bool IsVomiting(Player player) { object obj; if (player == null) { obj = null; } else { SEMan sEMan = ((Character)player).GetSEMan(); obj = ((sEMan != null) ? sEMan.GetStatusEffects() : null); } List<StatusEffect> list = (List<StatusEffect>)obj; if (list == null) { return false; } foreach (StatusEffect item in list) { if (item is SE_Puke) { return true; } } return false; } internal static bool CanAutomate(Player player) { if ((Object)(object)player != (Object)null && !((Character)player).IsDead() && !((Character)player).IsTeleporting() && !((Character)player).InCutscene()) { return !IsVomiting(player); } return false; } internal static bool ShouldPauseTimers(Player player) { if (PluginConfig.ModEnabled.Value && (Object)(object)player == (Object)(object)Player.m_localPlayer && CanAutomate(player)) { if (!PluginConfig.PauseOnBoat.Value || !((Character)player).IsAttachedToShip()) { if (PluginConfig.PauseNearWorkbench.Value) { return _nearWorkbench; } return false; } return true; } return false; } internal static float ScaleFoodRate(float rate, Player player) { if (!PluginConfig.ModEnabled.Value || (Object)(object)player != (Object)(object)Player.m_localPlayer) { return rate; } if (ShouldPauseTimers(player)) { return 0f; } return PantryPolicy.ScaleRate(rate, PluginConfig.ScaleWithTimeControl.Value ? GetTimeControlMultiplier() : 1f); } public static List<Food> CaptureFoodsForDeath(Player player) { if (!PluginConfig.ModEnabled.Value || !PluginConfig.KeepFoodOnDeath.Value || (Object)(object)player != (Object)(object)Player.m_localPlayer) { return null; } List<Food> foods = player.GetFoods(); if (foods == null || foods.Count == 0) { return null; } return new List<Food>(foods); } public static void RestoreFoodsAfterDeath(Player player, List<Food> saved) { if (saved != null && saved.Count != 0 && !((Object)(object)player == (Object)null)) { List<Food> foods = player.GetFoods(); if (foods != null) { foods.Clear(); foods.AddRange(saved); } } } private static void CheckFoods(Player player) { Inventory inventory = ((Humanoid)player).GetInventory(); List<Food> foods = player.GetFoods(); if (inventory == null || foods == null) { return; } _seenThisTick.Clear(); float foodRate = GetFoodRate(); for (int i = 0; i < foods.Count; i++) { Food val = foods[i]; if (val?.m_item?.m_shared == null) { continue; } string name = val.m_item.m_shared.m_name; _seenThisTick.Add(name); float foodBurnTime = val.m_item.m_shared.m_foodBurnTime; float num = ((foodBurnTime > 0f) ? (val.m_time / foodBurnTime * 100f) : 0f); if (!val.CanEatAgain()) { _expiryShown.Remove(name); _lowSupplyShown.Remove(name); continue; } if (PluginConfig.ExpiryNotify.Value && !_expiryShown.Contains(name) && num <= (float)PluginConfig.ExpiryPercent.Value) { string text = Localization.instance.Localize(name); int num2 = Mathf.CeilToInt(val.m_time / foodRate / 60f); ShowHud((num2 <= 1) ? ModLocalization.L("hearthpantry_expiry_almost", text) : ModLocalization.L("hearthpantry_expiry_mins", text, num2)); _expiryShown.Add(name); } if (!PluginConfig.AutoEat.Value || num > (float)PluginConfig.AutoEatPercent.Value) { continue; } ItemData item = inventory.GetItem(name, -1, false); if (item != null && !(item.m_shared.m_consumeStatusEffect is SE_Puke) && ((Humanoid)player).ConsumeItem(inventory, item, false)) { _expiryShown.Remove(name); if (PluginConfig.AutoEatNotify.Value) { string text2 = Localization.instance.Localize(name); ShowHud(ModLocalization.L("hearthpantry_reeaten", text2)); } MaybeLowSupply(inventory, name); } } _expiryShown.RemoveWhere((string n) => !_seenThisTick.Contains(n)); _lowSupplyShown.RemoveWhere((string n) => !_seenThisTick.Contains(n)); } private static void MaybeLowSupply(Inventory inventory, string name) { if (PluginConfig.LowSupplyNotify.Value && PluginConfig.LowSupplyCount.Value > 0 && !_lowSupplyShown.Contains(name)) { int num = inventory.CountItems(name, -1, true); if (num <= PluginConfig.LowSupplyCount.Value) { string text = Localization.instance.Localize(name); ShowHud((num <= 0) ? ModLocalization.L("hearthpantry_out", text) : ((num == 1) ? ModLocalization.L("hearthpantry_last", text) : ModLocalization.L("hearthpantry_low", num, text))); _lowSupplyShown.Add(name); } } } private static bool IsNearWorkbench(Player player) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) return (Object)(object)CraftingStation.FindClosestStationInRange("$piece_workbench", ((Component)player).transform.position, 20f) != (Object)null; } private static void ShowHud(string message) { if (!((Object)(object)MessageHud.instance == (Object)null)) { MessageHud.instance.ShowMessage((MessageType)2, message, 0, (Sprite)null, false, true); } } private static float GetFoodRate() { if (!_foodRateResolved) { _foodRateResolved = true; try { FieldInfo fieldInfo = AccessTools.Field(typeof(Game), "m_foodRate"); if (fieldInfo != null) { _foodRateRef = AccessTools.StaticFieldRefAccess<float>(fieldInfo); } } catch (Exception ex) { _foodRateRef = null; HearthPantryPlugin.Log.LogWarning((object)("resolving Game.m_foodRate failed: " + ex.Message)); } } if (_foodRateRef == null) { return 1f; } try { float num = ScaleFoodRate(_foodRateRef.Invoke(), Player.m_localPlayer); return (num > 0f) ? num : 1f; } catch { _foodRateRef = null; return 1f; } } private static float GetTimeControlMultiplier() { if (!_tcResolved) { _tcResolved = true; try { if (Chainloader.PluginInfos.ContainsKey("drummercraig.time_control")) { Type type = AccessTools.TypeByName("TimeControl.TimeControlConfig"); if (type != null) { _tcMultiplierGetter = AccessTools.PropertyGetter(type, "EffectiveMultiplier"); } HearthPantryPlugin.Log.LogInfo((object)((_tcMultiplierGetter != null) ? "TimeControl detected; food duration will scale with its multiplier." : "TimeControl detected but EffectiveMultiplier could not be resolved.")); } } catch (Exception ex) { _tcMultiplierGetter = null; HearthPantryPlugin.Log.LogWarning((object)("TimeControl detection failed: " + ex.Message)); } } if (_tcMultiplierGetter == null) { return 1f; } try { return PantryPolicy.ValidMultiplier((float)_tcMultiplierGetter.Invoke(null, null)); } catch { return 1f; } } } internal static class MeadManager { private const float MeleeRangeSq = 9f; private static float _lastEnemyHitTime = float.MinValue; private static readonly Queue<float> _frostHitTimes = new Queue<float>(); private static readonly List<Character> _nearby = new List<Character>(); private static Player _trackedPlayer; private static void RefreshState() { Player localPlayer = Player.m_localPlayer; if (localPlayer != _trackedPlayer || !PluginConfig.ModEnabled.Value || !FoodManager.CanAutomate(localPlayer)) { _trackedPlayer = localPlayer; _lastEnemyHitTime = float.MinValue; _frostHitTimes.Clear(); } float num = Time.time - PluginConfig.FrostMeadTickWindow.Value; while (_frostHitTimes.Count > 0 && _frostHitTimes.Peek() < num) { _frostHitTimes.Dequeue(); } if (!PluginConfig.AutoFrostMead.Value) { _frostHitTimes.Clear(); } } public static void OnDamage(Character character, HitData hit) { if ((Object)(object)character != (Object)(object)Player.m_localPlayer || hit == null) { return; } RefreshState(); if (!PluginConfig.ModEnabled.Value || !FoodManager.CanAutomate(Player.m_localPlayer)) { return; } Character attacker = hit.GetAttacker(); if ((Object)(object)attacker != (Object)null && !attacker.IsPlayer() && BaseAI.IsEnemy(attacker, character)) { _lastEnemyHitTime = Time.time; } if (PluginConfig.AutoFrostMead.Value && hit.m_damage.m_frost > 0f) { _frostHitTimes.Enqueue(Time.time); while (_frostHitTimes.Count > PluginConfig.FrostMeadTickCount.Value) { _frostHitTimes.Dequeue(); } } } public static void Tick() { RefreshState(); if (PluginConfig.ModEnabled.Value && FoodManager.CanAutomate(Player.m_localPlayer)) { CheckHealthMead(); CheckProximityResistMeads(); CheckFrostResistMead(); } } private static void CheckHealthMead() { if (!PluginConfig.AutoHealthMead.Value) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).GetHealthPercentage() * 100f >= (float)PluginConfig.HealthMeadThreshold.Value || (PluginConfig.HealthMeadOnlyEnemyHit.Value && Time.time - _lastEnemyHitTime > PluginConfig.HealthMeadEnemyHitWindow.Value)) { return; } Inventory inventory = ((Humanoid)localPlayer).GetInventory(); SEMan sEMan = ((Character)localPlayer).GetSEMan(); float maxHealth = ((Character)localPlayer).GetMaxHealth(); foreach (ItemData healthMead in GetHealthMeads(inventory)) { StatusEffect consumeStatusEffect = healthMead.m_shared.m_consumeStatusEffect; if (EffectAlreadyActive(sEMan, consumeStatusEffect)) { continue; } if (PluginConfig.HealthMeadRequireMaxHealth.Value) { SE_Stats val = (SE_Stats)(object)((consumeStatusEffect is SE_Stats) ? consumeStatusEffect : null); if (val != null && maxHealth < val.m_healthOverTime) { continue; } } if (((Humanoid)localPlayer).ConsumeItem(inventory, healthMead, false)) { if (PluginConfig.HealthMeadNotify.Value) { ShowHud(ModLocalization.L("hearthpantry_drank", Localized(healthMead))); } break; } } } private static void CheckProximityResistMeads() { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) bool value = PluginConfig.AutoPoisonMead.Value; bool value2 = PluginConfig.AutoFireMead.Value; if (!value && !value2) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } float num = 0f; if (value) { num = Mathf.Max(num, PluginConfig.PoisonMeadRange.Value); } if (value2) { num = Mathf.Max(num, PluginConfig.FireMeadRange.Value); } bool flag = false; bool flag2 = false; _nearby.Clear(); Character.GetCharactersInRange(((Component)localPlayer).transform.position, num, _nearby); float num2 = PluginConfig.PoisonMeadRange.Value * PluginConfig.PoisonMeadRange.Value; float num3 = PluginConfig.FireMeadRange.Value * PluginConfig.FireMeadRange.Value; foreach (Character item in _nearby) { if ((Object)(object)item == (Object)null || item.IsPlayer() || item.IsDead()) { continue; } Vector3 val = ((Component)item).transform.position - ((Component)localPlayer).transform.position; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (IsThreatening(item, localPlayer, sqrMagnitude)) { GetThreatDamage(item, out var poison, out var fire); if (value && !flag && sqrMagnitude <= num2 && poison) { flag = true; } if (value2 && !flag2 && sqrMagnitude <= num3 && fire) { flag2 = true; } if (flag && flag2) { break; } } } if (flag) { ConsumeResistMead(localPlayer, PluginConfig.PoisonMeadNotify, (DamageType)256); } if (flag2) { ConsumeResistMead(localPlayer, PluginConfig.FireMeadNotify, (DamageType)32); } } private static void CheckFrostResistMead() { if (!PluginConfig.AutoFrostMead.Value) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } float num = Time.time - PluginConfig.FrostMeadTickWindow.Value; while (_frostHitTimes.Count > 0 && _frostHitTimes.Peek() < num) { _frostHitTimes.Dequeue(); } SEMan sEMan = ((Character)localPlayer).GetSEMan(); bool num2 = sEMan.HaveStatusEffect(StringExtensionMethods.GetStableHashCode("Freezing")); bool flag = _frostHitTimes.Count >= PluginConfig.FrostMeadTickCount.Value; if (!num2 && !flag) { return; } Inventory inventory = ((Humanoid)localPlayer).GetInventory(); foreach (ItemData resistMead in GetResistMeads(inventory, (DamageType)64)) { StatusEffect consumeStatusEffect = resistMead.m_shared.m_consumeStatusEffect; if (EffectAlreadyActive(sEMan, consumeStatusEffect)) { _frostHitTimes.Clear(); break; } if (((Humanoid)localPlayer).ConsumeItem(inventory, resistMead, false)) { _frostHitTimes.Clear(); if (PluginConfig.FrostMeadNotify.Value) { ShowHud(ModLocalization.L("hearthpantry_drank", Localized(resistMead))); } break; } } } private static void ConsumeResistMead(Player player, ConfigEntry<bool> notify, DamageType type) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) Inventory inventory = ((Humanoid)player).GetInventory(); SEMan sEMan = ((Character)player).GetSEMan(); foreach (ItemData resistMead in GetResistMeads(inventory, type)) { StatusEffect consumeStatusEffect = resistMead.m_shared.m_consumeStatusEffect; if (EffectAlreadyActive(sEMan, consumeStatusEffect)) { break; } if (((Humanoid)player).ConsumeItem(inventory, resistMead, false)) { if (notify.Value) { ShowHud(ModLocalization.L("hearthpantry_drank", Localized(resistMead))); } break; } } } private static bool IsThreatening(Character c, Player localPlayer, float distSq) { if (!BaseAI.IsEnemy(c, (Character)(object)localPlayer)) { return false; } if (distSq <= 9f) { return true; } MonsterAI component = ((Component)c).GetComponent<MonsterAI>(); if ((Object)(object)component != (Object)null) { return (Object)(object)((BaseAI)component).GetTargetCreature() == (Object)(object)localPlayer; } return false; } private static bool EffectAlreadyActive(SEMan seman, StatusEffect effect) { if ((Object)(object)effect != (Object)null) { if (!seman.HaveStatusEffect(effect.NameHash())) { if (!string.IsNullOrEmpty(effect.m_category)) { return seman.HaveStatusEffectCategory(effect.m_category); } return false; } return true; } return false; } private static void GetThreatDamage(Character c, out bool poison, out bool fire) { poison = false; fire = false; Humanoid val = (Humanoid)(object)((c is Humanoid) ? c : null); if (val == null) { return; } foreach (ItemData allItem in val.GetInventory().GetAllItems()) { if (allItem.m_shared.m_damages.m_poison > 0f) { poison = true; } if (allItem.m_shared.m_damages.m_fire > 0f) { fire = true; } } ItemData currentWeapon = val.GetCurrentWeapon(); if (currentWeapon != null) { if (currentWeapon.m_shared.m_damages.m_poison > 0f) { poison = true; } if (currentWeapon.m_shared.m_damages.m_fire > 0f) { fire = true; } } } private static List<ItemData> GetHealthMeads(Inventory inventory) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 List<ItemData> list = new List<ItemData>(); foreach (ItemData allItem in inventory.GetAllItems()) { if ((int)allItem.m_shared.m_itemType == 2 && !(allItem.m_shared.m_consumeStatusEffect is SE_Puke)) { StatusEffect consumeStatusEffect = allItem.m_shared.m_consumeStatusEffect; SE_Stats val = (SE_Stats)(object)((consumeStatusEffect is SE_Stats) ? consumeStatusEffect : null); if (val != null && val.m_healthOverTime > 0f) { list.Add(allItem); } } } list.Sort(delegate(ItemData a, ItemData b) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) float healthOverTime = ((SE_Stats)a.m_shared.m_consumeStatusEffect).m_healthOverTime; float healthOverTime2 = ((SE_Stats)b.m_shared.m_consumeStatusEffect).m_healthOverTime; return healthOverTime2.CompareTo(healthOverTime); }); return list; } private static List<ItemData> GetResistMeads(Inventory inventory, DamageType type) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) List<ItemData> list = new List<ItemData>(); foreach (ItemData allItem in inventory.GetAllItems()) { if ((int)allItem.m_shared.m_itemType != 2 || allItem.m_shared.m_consumeStatusEffect is SE_Puke) { continue; } StatusEffect consumeStatusEffect = allItem.m_shared.m_consumeStatusEffect; SE_Stats val = (SE_Stats)(object)((consumeStatusEffect is SE_Stats) ? consumeStatusEffect : null); if (val == null) { continue; } foreach (DamageModPair mod in val.m_mods) { if (mod.m_type == type && IsProtective(mod.m_modifier)) { list.Add(allItem); break; } } } return list; } internal static bool IsProtective(DamageModifier modifier) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 if ((int)modifier != 1 && (int)modifier != 5 && (int)modifier != 7) { return (int)modifier == 3; } return true; } private static string Localized(ItemData item) { return Localization.instance.Localize(item.m_shared.m_name); } private static void ShowHud(string message) { if (!((Object)(object)MessageHud.instance == (Object)null)) { MessageHud.instance.ShowMessage((MessageType)2, message, 0, (Sprite)null, false, true); } } } internal static class ModLocalization { private static readonly string[] Languages = new string[17] { "English", "Portuguese_Brazilian", "Portuguese_European", "German", "French", "Spanish", "Russian", "Polish", "Dutch", "Italian", "Swedish", "Turkish", "Ukrainian", "Chinese", "Chinese_Trad", "Japanese", "Korean" }; internal static void Register() { CustomLocalization localization = LocalizationManager.Instance.GetLocalization(); int num = 0; bool flag = false; string[] languages = Languages; foreach (string text in languages) { if (TryLoad(localization, text)) { num++; if (text == "English") { flag = true; } } } if (!flag) { RegisterInlineEnglish(localization); Logger.LogWarning((object)"HearthPantry: Translations folder missing — English inline fallback"); } else { Logger.LogInfo((object)$"HearthPantry: loaded localization for {num} language(s)"); } } private static bool TryLoad(CustomLocalization loc, string language) { try { string text = FindPath(language); if (string.IsNullOrEmpty(text) || !File.Exists(text)) { return false; } string text2 = File.ReadAllText(text); if (string.IsNullOrEmpty(text2)) { return false; } loc.AddJsonFile(language, text2); return true; } catch (Exception ex) { Logger.LogWarning((object)("HearthPantry: failed loading " + language + " — " + ex.Message)); return false; } } private static string FindPath(string language) { string path = Path.Combine("Translations", language, "hearthpantry.json"); string text = Path.Combine(Path.GetDirectoryName(typeof(HearthPantryPlugin).Assembly.Location) ?? "", path); if (File.Exists(text)) { return text; } string text2 = Path.Combine(Directory.GetCurrentDirectory(), path); if (!File.Exists(text2)) { return null; } return text2; } private static void RegisterInlineEnglish(CustomLocalization loc) { string text = "English"; loc.AddTranslation(ref text, new Dictionary<string, string> { { "hearthpantry_expiry_almost", "{0} is almost gone" }, { "hearthpantry_expiry_mins", "{0} is running low — about {1} min left" }, { "hearthpantry_reeaten", "Had another {0}" }, { "hearthpantry_ate_fill", "Ate {0} from the pantry" }, { "hearthpantry_out", "Out of {0}" }, { "hearthpantry_last", "Last {0} in the bag" }, { "hearthpantry_low", "Only {0} {1} left" }, { "hearthpantry_drank", "Drank {0}" }, { "hearthpantry_toggle_on", "Pantry is watching your food" }, { "hearthpantry_toggle_off", "Pantry paused — eat when you want" } }); } internal static string L(string token, params object[] args) { string text = ((Localization.instance != null) ? Localization.instance.Localize("$" + token) : ("$" + token)); if (args == null || args.Length == 0) { return text; } try { return string.Format(text, args); } catch { return text; } } } internal static class PantryPolicy { internal static float ValidMultiplier(float multiplier) { if (!float.IsNaN(multiplier) && !float.IsInfinity(multiplier) && !(multiplier <= 0f)) { return multiplier; } return 1f; } internal static float ScaleRate(float rate, float multiplier) { return rate / ValidMultiplier(multiplier); } } [HarmonyPatch] internal static class Patches { private static readonly MethodInfo TotalFood = AccessTools.Method(typeof(Player), "GetTotalFoodValue", (Type[])null, (Type[])null); private static readonly MethodInfo SetMaxEitr = AccessTools.Method(typeof(Player), "SetMaxEitr", new Type[2] { typeof(float), typeof(bool) }, (Type[])null); [HarmonyTranspiler] [HarmonyPatch(typeof(Player), "UpdateFood")] private static IEnumerable<CodeInstruction> UpdateFood_Transpiler(IEnumerable<CodeInstruction> instructions) { FieldInfo rate = AccessTools.Field(typeof(Game), "m_foodRate"); MethodInfo scale = AccessTools.Method(typeof(FoodManager), "ScaleFoodRate", (Type[])null, (Type[])null); foreach (CodeInstruction instruction in instructions) { yield return instruction; if (CodeInstructionExtensions.LoadsField(instruction, rate, false)) { yield return new CodeInstruction(OpCodes.Ldarg_0, (object)null); yield return new CodeInstruction(OpCodes.Call, (object)scale); } } } [HarmonyPrefix] [HarmonyPatch(typeof(Player), "UpdateFood")] private static void UpdateFood_Prefix(Player __instance, out Dictionary<Food, float> __state) { __state = null; if (!FoodManager.ShouldPauseTimers(__instance)) { return; } __state = new Dictionary<Food, float>(); foreach (Food food in __instance.GetFoods()) { __state[food] = food.m_time; } } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "UpdateFood")] private static void UpdateFood_Postfix(Player __instance, Dictionary<Food, float> __state) { if (__state == null) { return; } List<Food> foods = __instance.GetFoods(); foreach (KeyValuePair<Food, float> item in __state) { Food key = item.Key; key.m_time = item.Value; if (!foods.Contains(key)) { foods.Add(key); } float num = Mathf.Pow(Mathf.Clamp01(key.m_time / key.m_item.m_shared.m_foodBurnTime), 0.3f); key.m_health = key.m_item.m_shared.m_food * num; key.m_stamina = key.m_item.m_shared.m_foodStamina * num; key.m_eitr = key.m_item.m_shared.m_foodEitr * num; } object[] array = new object[3] { 0f, 0f, 0f }; TotalFood.Invoke(__instance, array); __instance.SetMaxHealth((float)array[0], false); __instance.SetMaxStamina((float)array[1], false); SetMaxEitr.Invoke(__instance, new object[2] { array[2], false }); } [HarmonyPrefix] [HarmonyPatch(typeof(Player), "OnDeath")] private static void OnDeath_Prefix(Player __instance, out List<Food> __state) { __state = FoodManager.CaptureFoodsForDeath(__instance); } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "OnDeath")] private static void OnDeath_Postfix(Player __instance, List<Food> __state) { FoodManager.RestoreFoodsAfterDeath(__instance, __state); } [HarmonyPrefix] [HarmonyPatch(typeof(Character), "ApplyDamage")] private static void ApplyDamage_Prefix(Character __instance, out float __state) { __state = (((Object)(object)__instance == (Object)(object)Player.m_localPlayer) ? __instance.GetHealth() : float.NaN); } [HarmonyPostfix] [HarmonyPatch(typeof(Character), "ApplyDamage")] private static void ApplyDamage_Postfix(Character __instance, HitData hit, float __state) { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && __instance.GetHealth() < __state) { MeadManager.OnDamage(__instance, hit); } } } [BepInPlugin("com.blackhearthx.hearthpantry", "HearthPantry", "1.0.4")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class HearthPantryPlugin : BaseUnityPlugin { public const string PluginGUID = "com.blackhearthx.hearthpantry"; public const string PluginName = "HearthPantry"; public const string PluginVersion = "1.0.4"; internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; PluginConfig.Bind(((BaseUnityPlugin)this).Config); ModLocalization.Register(); new Harmony("com.blackhearthx.hearthpantry").PatchAll(typeof(HearthPantryPlugin).Assembly); ((MonoBehaviour)this).StartCoroutine(FoodLoop()); ((MonoBehaviour)this).StartCoroutine(MeadLoop()); Logger.LogInfo((object)"HearthPantry 1.0.4 loaded"); } private void Update() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (!PluginConfig.ModEnabled.Value) { return; } KeyboardShortcut value = PluginConfig.ToggleHotkey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { PluginConfig.AutoEat.Value = !PluginConfig.AutoEat.Value; if ((Object)(object)MessageHud.instance != (Object)null) { string text = (PluginConfig.AutoEat.Value ? ModLocalization.L("hearthpantry_toggle_on") : ModLocalization.L("hearthpantry_toggle_off")); MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true); } } } private IEnumerator FoodLoop() { WaitForSeconds wait = new WaitForSeconds(1f); while (true) { yield return wait; try { FoodManager.Tick(); } catch (Exception arg) { Log.LogError((object)$"FoodManager tick failed: {arg}"); } } } private IEnumerator MeadLoop() { WaitForSeconds wait = new WaitForSeconds(0.5f); while (true) { yield return wait; try { MeadManager.Tick(); } catch (Exception arg) { Log.LogError((object)$"MeadManager tick failed: {arg}"); } } } } internal static class PluginConfig { public static ConfigEntry<bool> ModEnabled; public static ConfigEntry<bool> AutoEat; public static ConfigEntry<int> AutoEatPercent; public static ConfigEntry<bool> AutoEatNotify; public static ConfigEntry<bool> ExpiryNotify; public static ConfigEntry<int> ExpiryPercent; public static ConfigEntry<bool> LowSupplyNotify; public static ConfigEntry<int> LowSupplyCount; public static ConfigEntry<bool> PauseNearWorkbench; public static ConfigEntry<bool> PauseOnBoat; public static ConfigEntry<bool> KeepFoodOnDeath; public static ConfigEntry<bool> ScaleWithTimeControl; public static ConfigEntry<KeyboardShortcut> ToggleHotkey; public static ConfigEntry<bool> AutoHealthMead; public static ConfigEntry<int> HealthMeadThreshold; public static ConfigEntry<bool> HealthMeadOnlyEnemyHit; public static ConfigEntry<float> HealthMeadEnemyHitWindow; public static ConfigEntry<bool> HealthMeadRequireMaxHealth; public static ConfigEntry<bool> HealthMeadNotify; public static ConfigEntry<bool> AutoPoisonMead; public static ConfigEntry<float> PoisonMeadRange; public static ConfigEntry<bool> PoisonMeadNotify; public static ConfigEntry<bool> AutoFireMead; public static ConfigEntry<float> FireMeadRange; public static ConfigEntry<bool> FireMeadNotify; public static ConfigEntry<bool> AutoFrostMead; public static ConfigEntry<int> FrostMeadTickCount; public static ConfigEntry<float> FrostMeadTickWindow; public static ConfigEntry<bool> FrostMeadNotify; public static void Bind(ConfigFile config) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Expected O, but got Unknown //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Expected O, but got Unknown //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Expected O, but got Unknown //IL_02af: Unknown result type (might be due to invalid IL or missing references) //IL_02b9: Expected O, but got Unknown //IL_031d: Unknown result type (might be due to invalid IL or missing references) //IL_0327: Expected O, but got Unknown //IL_0380: Unknown result type (might be due to invalid IL or missing references) //IL_038a: Expected O, but got Unknown //IL_03b8: Unknown result type (might be due to invalid IL or missing references) //IL_03c2: Expected O, but got Unknown ModEnabled = config.Bind<bool>("General", "01. Mod Enabled", true, "Turns the whole pantry on or off."); AutoEat = config.Bind<bool>("General", "02. Auto-Eat", true, "Reaches into your bag and eats for you when a meal is about to fade."); AutoEatPercent = config.Bind<int>("General", "03. Auto-Eat Percent", 45, new ConfigDescription("How much of the timer is left when it re-eats. The game only lets you eat again once the food starts flashing (50%). Forty-five keeps the buff near full without wasting a stack on the last seconds.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 49), Array.Empty<object>())); AutoEatNotify = config.Bind<bool>("General", "04. Auto-Eat Notify", true, "Shows a short line on screen when the pantry eats for you."); ExpiryNotify = config.Bind<bool>("General", "07. Expiry Notify", true, "A quiet heads-up when a meal is getting thin, even if you prefer to eat by hand."); ExpiryPercent = config.Bind<int>("General", "08. Expiry Percent", 50, new ConfigDescription("When that heads-up appears, as a percent of time left. Keep it at or above Auto-Eat Percent so you hear about it before the pantry acts.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 50), Array.Empty<object>())); LowSupplyNotify = config.Bind<bool>("General", "09. Low Supply Notify", true, "Warns you when a stack is nearly gone after the pantry eats from it."); LowSupplyCount = config.Bind<int>("General", "10. Low Supply Count", 1, "How many pieces left counts as \"running low\"."); PauseNearWorkbench = config.Bind<bool>("General", "11. Pause Near Workbench", true, "While you are at the workbench, food timers hold still. Healing and stamina from the meal still work."); PauseOnBoat = config.Bind<bool>("General", "12. Pause On Boat", true, "Same pause while you are sailing. Effects keep ticking; only the countdown freezes."); KeepFoodOnDeath = config.Bind<bool>("General", "13. Keep Food On Death", false, "Come back from death with the same meals still in your belly. Off by default so death still costs something."); ScaleWithTimeControl = config.Bind<bool>("General", "14. Scale With TimeControl", true, "If you use DrummerCraig's TimeControl, food lasts in step with the longer day. Harmless if that mod is not installed."); ToggleHotkey = config.Bind<KeyboardShortcut>("General", "15. Toggle Hotkey", new KeyboardShortcut((KeyCode)116, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }), "Quick key to pause or resume the pantry without opening the config."); AutoHealthMead = config.Bind<bool>("Health Mead", "01. Auto Health Mead", true, "Drinks a healing mead when you are hurt badly enough."); HealthMeadThreshold = config.Bind<int>("Health Mead", "02. Health Threshold", 30, new ConfigDescription("Drink when your health falls below this percent.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 99), Array.Empty<object>())); HealthMeadOnlyEnemyHit = config.Bind<bool>("Health Mead", "03. Only On Enemy Hit", true, "Only after an enemy hit you, so a fall or lava does not waste the bottle."); HealthMeadEnemyHitWindow = config.Bind<float>("Health Mead", "04. Enemy Hit Window", 5f, new ConfigDescription("How many seconds after that hit the mead is still allowed to fire.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), Array.Empty<object>())); HealthMeadRequireMaxHealth = config.Bind<bool>("Health Mead", "05. Require Sufficient Max Health", false, "Skip a mead that heals more than your max health and try a smaller one instead."); HealthMeadNotify = config.Bind<bool>("Health Mead", "06. Notify", true, "Tell you when a health mead was drunk."); AutoPoisonMead = config.Bind<bool>("Poison Resist Mead", "01. Auto Poison Mead", true, "Drinks poison resist when something venomous is coming for you."); PoisonMeadRange = config.Bind<float>("Poison Resist Mead", "02. Detection Range", 15f, new ConfigDescription("How far to look for that kind of threat.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 50f), Array.Empty<object>())); PoisonMeadNotify = config.Bind<bool>("Poison Resist Mead", "03. Notify", true, "Tell you when poison resist was drunk."); AutoFireMead = config.Bind<bool>("Fire Resist Mead", "01. Auto Fire Mead", true, "Drinks fire resist when something fiery is on you."); FireMeadRange = config.Bind<float>("Fire Resist Mead", "02. Detection Range", 15f, new ConfigDescription("How far to look for fire threats.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 50f), Array.Empty<object>())); FireMeadNotify = config.Bind<bool>("Fire Resist Mead", "03. Notify", true, "Tell you when fire resist was drunk."); AutoFrostMead = config.Bind<bool>("Frost Resist Mead", "01. Auto Frost Mead", true, "Drinks frost resist when you are freezing or taking repeated frost hits."); FrostMeadTickCount = config.Bind<int>("Frost Resist Mead", "02. Frost Tick Count", 3, new ConfigDescription("How many frost hits in the window before it drinks.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>())); FrostMeadTickWindow = config.Bind<float>("Frost Resist Mead", "03. Frost Tick Window", 10f, new ConfigDescription("Seconds those frost hits are counted across.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 60f), Array.Empty<object>())); FrostMeadNotify = config.Bind<bool>("Frost Resist Mead", "04. Notify", true, "Tell you when frost resist was drunk."); } } }