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 ThorsHeartyMeals v1.0.0
BepInEx/plugins/ThorsHeartyMeals/ThorsHeartyMeals.dll
Decompiled 5 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using ThorsHeartyMeals.Config; using ThorsHeartyMeals.Core; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("lemigu")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Eat the same food ten times to keep its buff permanently, until you die or eat a Bukeperries.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+b96cebdb2d597b2f00447d5868a9fc4fe12349a8")] [assembly: AssemblyProduct("Thor's Hearty Meals")] [assembly: AssemblyTitle("ThorsHeartyMeals")] [assembly: AssemblyVersion("1.0.0.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 ThorsHeartyMeals { [BepInPlugin("lemigu.thors_hearty_meals", "Thor's Hearty Meals", "1.0.0")] public class Plugin : BaseUnityPlugin { public const string ModGuid = "lemigu.thors_hearty_meals"; public const string ModName = "Thor's Hearty Meals"; public const string ModVersion = "1.0.0"; private Harmony _harmony; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; ModSettings.Bind(((BaseUnityPlugin)this).Config); _harmony = new Harmony("lemigu.thors_hearty_meals"); _harmony.PatchAll(Assembly.GetExecutingAssembly()); Log.LogInfo((object)"Thor's Hearty Meals v1.0.0 loaded."); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } } namespace ThorsHeartyMeals.Patches { [HarmonyPatch(typeof(Player), "CanEat")] internal static class CanEatPatch { [HarmonyPrefix] private static bool Prefix(Player __instance, ItemData item, ref bool __result) { if ((Object)(object)__instance == (Object)null || item?.m_shared == null || item.m_shared.m_food <= 0f) { return true; } MealState mealState = MealStates.Peek(__instance); if (mealState == null) { return true; } string foodKey = FoodHelper.GetFoodKey(item); if (string.IsNullOrEmpty(foodKey) || mealState.IsPermanent(foodKey)) { return true; } foreach (Food food in __instance.GetFoods()) { if (food.m_name == foodKey) { __result = true; return false; } } return true; } } [HarmonyPatch(typeof(Player), "ConsumeItem")] internal static class ConsumeItemPatch { [HarmonyPostfix] private static void Postfix(Player __instance, ItemData item, bool __result) { if (!__result || item == null || !ResetItem.IsResetItem(FoodHelper.GetFoodKey(item))) { return; } MealState mealState = MealStates.Get(__instance); if (mealState != null) { bool hasPermanent = mealState.HasPermanent; FoodHelper.RemovePermanentFoods(__instance, mealState); mealState.ClearPermanents(); if (ModSettings.BukeperriesClearsEatProgress.Value) { mealState.Eats.Clear(); } FoodHelper.Refresh(__instance); ((Character)__instance).Message((MessageType)2, hasPermanent ? "You retch and vomit up your permanent meals." : "You feel queasy, but nothing comes up.", 0, (Sprite)null, false); MealStates.Save(__instance); } } } [HarmonyPatch(typeof(Player), "OnDeath")] internal static class DeathPatch { [HarmonyPostfix] private static void Postfix(Player __instance) { MealState mealState = MealStates.Peek(__instance); if (mealState != null) { mealState.ClearPermanents(); if (ModSettings.ResetProgressOnDeath.Value) { mealState.Eats.Clear(); } MealStates.Save(__instance); } } } [HarmonyPatch(typeof(Player), "EatFood")] internal static class EatFoodPatch { [HarmonyPrefix] private static void Prefix(Player __instance, ItemData item) { if ((Object)(object)__instance == (Object)null || item?.m_shared == null || item.m_shared.m_food <= 0f) { return; } MealState mealState = MealStates.Peek(__instance); if (mealState == null) { return; } string foodKey = FoodHelper.GetFoodKey(item); if (string.IsNullOrEmpty(foodKey) || mealState.IsPermanent(foodKey)) { return; } foreach (Food food in __instance.GetFoods()) { if (food.m_name == foodKey && !food.CanEatAgain()) { food.m_time = 0f; break; } } } [HarmonyPostfix] private static void Postfix(Player __instance, ItemData item, bool __result) { if (!__result || item == null) { return; } MealState mealState = MealStates.Get(__instance); if (mealState == null) { return; } string foodKey = FoodHelper.GetFoodKey(item); if (string.IsNullOrEmpty(foodKey) || ResetItem.IsResetItem(foodKey) || mealState.IsPermanent(foodKey)) { return; } int value = ModSettings.EatsRequired.Value; int num = mealState.IncrementEat(foodKey); string text = LocalizeName(item); if (num >= value) { mealState.MakePermanent(foodKey); FoodHelper.MaintainPermanentFoods(__instance, mealState); FoodHelper.Refresh(__instance); ((Character)__instance).Message((MessageType)2, "Your body has adapted: " + text + " now sustains you permanently.", 0, (Sprite)null, false); if (ModSettings.GrantBukeperriesOnUnlock.Value) { ResetItem.Grant(__instance, 1); } } else if (ModSettings.ShowProgressMessages.Value) { ((Character)__instance).Message((MessageType)2, $"{text} eaten {num} time(s) toward a permanent meal.", 0, (Sprite)null, false); } MealStates.Save(__instance); } private static string LocalizeName(ItemData item) { string text = item.m_shared?.m_name; if (string.IsNullOrEmpty(text)) { text = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : "food"); } Localization instance = Localization.instance; if (instance == null) { return text; } return instance.Localize(text); } } [HarmonyPatch(typeof(Hud), "UpdateFood")] internal static class HudFoodPatch { private static readonly Color PermanentColor = new Color(1f, 0.85f, 0.25f, 1f); [HarmonyPostfix] private static void Postfix(Hud __instance, Player player) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)player == (Object)null) { return; } MealState mealState = MealStates.Peek(player); if (mealState == null || !mealState.HasPermanent || __instance.m_foodTime == null) { return; } List<Food> foods = player.GetFoods(); if (foods == null) { return; } int num = Mathf.Min(foods.Count, __instance.m_foodTime.Length); for (int i = 0; i < num; i++) { if (mealState.IsPermanent(foods[i].m_name)) { TMP_Text val = __instance.m_foodTime[i]; if (!((Object)(object)val == (Object)null)) { val.text = (InfinityGlyph.CanDisplay(val) ? '∞'.ToString() : "Inf"); ((Graphic)val).color = PermanentColor; } } } } } [HarmonyPatch(typeof(Player), "Save")] internal static class SavePatch { [HarmonyPrefix] private static void Prefix(Player __instance) { MealStates.Save(__instance); } } [HarmonyPatch(typeof(Player), "Load")] internal static class LoadPatch { [HarmonyPostfix] private static void Postfix(Player __instance) { MealStates.Reload(__instance); } } [HarmonyPatch(typeof(Player), "OnDestroy")] internal static class PlayerDestroyPatch { [HarmonyPostfix] private static void Postfix(Player __instance) { MealStates.Forget(__instance); } } [HarmonyPatch(typeof(Player), "UpdateFood")] internal static class UpdateFoodPatch { [HarmonyPrefix] private static void Prefix(Player __instance) { MealState mealState = MealStates.Peek(__instance); if (mealState != null && mealState.HasPermanent) { FoodHelper.MaintainPermanentFoods(__instance, mealState); } } } } namespace ThorsHeartyMeals.Core { internal static class FoodHelper { private const float PermanentTimeMultiplier = 1000000f; private static MethodInfo _updateFoodMethod; private static MethodInfo UpdateFoodMethod => _updateFoodMethod ?? (_updateFoodMethod = AccessTools.Method(typeof(Player), "UpdateFood", (Type[])null, (Type[])null)); internal static string GetFoodKey(ItemData item) { if (item == null) { return null; } if ((Object)(object)item.m_dropPrefab != (Object)null) { return ((Object)item.m_dropPrefab).name; } return item.m_shared?.m_name; } internal static void Refresh(Player player) { try { UpdateFoodMethod?.Invoke(player, new object[2] { 0f, true }); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Failed to refresh food values: " + ex.Message)); } } } internal static void MaintainPermanentFoods(Player player, MealState state) { if ((Object)(object)player == (Object)null || state == null || state.Permanent.Count == 0) { return; } List<Food> foods = player.GetFoods(); if (foods == null) { return; } foreach (string item in state.Permanent) { Food val = null; foreach (Food item2 in foods) { if (item2.m_name == item) { val = item2; break; } } if (val == null) { if (foods.Count >= 3) { continue; } val = CreateFood(item); if (val == null) { continue; } foods.Add(val); } Fill(val); } } internal static void RemovePermanentFoods(Player player, MealState state) { if ((Object)(object)player == (Object)null || state == null) { return; } List<Food> foods = player.GetFoods(); if (foods == null) { return; } for (int num = foods.Count - 1; num >= 0; num--) { if (state.Permanent.Contains(foods[num].m_name)) { foods.RemoveAt(num); } } } private static void Fill(Food food) { if (food?.m_item?.m_shared != null) { SharedData shared = food.m_item.m_shared; float num = ((shared.m_foodBurnTime > 0f) ? shared.m_foodBurnTime : 1f); food.m_time = num * 1000000f; food.m_health = shared.m_food; food.m_stamina = shared.m_foodStamina; food.m_eitr = shared.m_foodEitr; } } private static Food CreateFood(string prefabName) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown //IL_0067: Expected O, but got Unknown ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null) { return null; } GameObject itemPrefab = instance.GetItemPrefab(prefabName); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null); if (val?.m_itemData?.m_shared == null) { return null; } Food val2 = new Food { m_name = prefabName, m_item = val.m_itemData }; Fill(val2); return val2; } } internal static class InfinityGlyph { internal const char Character = '∞'; private static readonly string[] OsFontCandidates = new string[9] { "Arial", "Segoe UI", "Tahoma", "Verdana", "Calibri", "DejaVu Sans", "Liberation Sans", "Noto Sans", "FreeSans" }; private static readonly Dictionary<TMP_FontAsset, bool> Resolved = new Dictionary<TMP_FontAsset, bool>(); private static bool _fallbackResolved; private static TMP_FontAsset _fallback; internal static bool CanDisplay(TMP_Text text) { TMP_FontAsset val = ((text != null) ? text.font : null); if ((Object)(object)val == (Object)null) { return false; } if (Resolved.TryGetValue(val, out var value)) { return value; } bool flag = Resolve(val); Resolved[val] = flag; return flag; } private static bool Resolve(TMP_FontAsset font) { if (font.HasCharacter('∞', true, true)) { return true; } TMP_FontAsset fallback = GetFallback(); if ((Object)(object)fallback == (Object)null) { return false; } if (font.fallbackFontAssetTable == null) { font.fallbackFontAssetTable = new List<TMP_FontAsset>(); } if (!font.fallbackFontAssetTable.Contains(fallback)) { font.fallbackFontAssetTable.Add(fallback); } return true; } private static TMP_FontAsset GetFallback() { if (_fallbackResolved) { return _fallback; } _fallbackResolved = true; try { Font val = Font.CreateDynamicFontFromOSFont(OsFontCandidates, 32); if ((Object)(object)val != (Object)null) { TMP_FontAsset val2 = TMP_FontAsset.CreateFontAsset(val); if ((Object)(object)val2 != (Object)null && val2.HasCharacter('∞', true, true)) { _fallback = val2; } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Could not create an infinity fallback font: " + ex.Message)); } } return _fallback; } } internal sealed class MealState { internal readonly Dictionary<string, int> Eats = new Dictionary<string, int>(StringComparer.Ordinal); internal readonly HashSet<string> Permanent = new HashSet<string>(StringComparer.Ordinal); internal bool HasPermanent => Permanent.Count > 0; internal int GetEatCount(string key) { if (!Eats.TryGetValue(key, out var value)) { return 0; } return value; } internal int IncrementEat(string key) { int num = GetEatCount(key) + 1; Eats[key] = num; return num; } internal bool IsPermanent(string key) { return Permanent.Contains(key); } internal void MakePermanent(string key) { Permanent.Add(key); } internal void ClearPermanents() { Permanent.Clear(); } internal void ClearAll() { Eats.Clear(); Permanent.Clear(); } } [Serializable] internal sealed class MealStateSave { public List<string> eatNames = new List<string>(); public List<int> eatCounts = new List<int>(); public List<string> permanent = new List<string>(); } internal static class MealStateSerializer { internal static string Serialize(MealState state) { MealStateSave mealStateSave = new MealStateSave(); foreach (KeyValuePair<string, int> eat in state.Eats) { mealStateSave.eatNames.Add(eat.Key); mealStateSave.eatCounts.Add(eat.Value); } mealStateSave.permanent.AddRange(state.Permanent); return JsonUtility.ToJson((object)mealStateSave); } internal static MealState Deserialize(string json) { MealState mealState = new MealState(); if (string.IsNullOrEmpty(json)) { return mealState; } MealStateSave mealStateSave; try { mealStateSave = JsonUtility.FromJson<MealStateSave>(json); } catch (Exception) { return mealState; } if (mealStateSave == null) { return mealState; } int num = Math.Min(mealStateSave.eatNames.Count, mealStateSave.eatCounts.Count); for (int i = 0; i < num; i++) { if (!string.IsNullOrEmpty(mealStateSave.eatNames[i])) { mealState.Eats[mealStateSave.eatNames[i]] = mealStateSave.eatCounts[i]; } } if (mealStateSave.permanent != null) { foreach (string item in mealStateSave.permanent) { if (!string.IsNullOrEmpty(item)) { mealState.Permanent.Add(item); } } } return mealState; } } internal static class MealStates { internal const string SaveKey = "thors_hearty_meals"; private static readonly Dictionary<Player, MealState> States = new Dictionary<Player, MealState>(); internal static MealState Get(Player player) { if ((Object)(object)player == (Object)null) { return null; } if (!States.TryGetValue(player, out var value)) { value = Load(player); States[player] = value; } return value; } internal static MealState Peek(Player player) { if ((Object)(object)player == (Object)null) { return null; } if (!States.TryGetValue(player, out var value)) { return null; } return value; } internal static void Save(Player player) { if (!((Object)(object)player == (Object)null) && player.m_customData != null && States.TryGetValue(player, out var value)) { player.m_customData["thors_hearty_meals"] = MealStateSerializer.Serialize(value); } } internal static void Reload(Player player) { if (!((Object)(object)player == (Object)null)) { States[player] = Load(player); } } internal static void Forget(Player player) { if ((Object)(object)player != (Object)null) { States.Remove(player); } } private static MealState Load(Player player) { if (player?.m_customData != null && player.m_customData.TryGetValue("thors_hearty_meals", out var value)) { return MealStateSerializer.Deserialize(value); } return new MealState(); } } internal static class ResetItem { internal static bool IsResetItem(string key) { if (string.IsNullOrEmpty(key)) { return false; } string[] array = ModSettings.BukeperriesItemNames.Value.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0 && string.Equals(key, text, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } internal static bool TryGetPrefab(out GameObject prefab) { prefab = null; ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null) { return false; } string[] array = ModSettings.BukeperriesItemNames.Value.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { GameObject itemPrefab = instance.GetItemPrefab(text); if ((Object)(object)itemPrefab != (Object)null) { prefab = itemPrefab; return true; } } } return false; } internal static void Grant(Player player, int amount) { if (!((Object)(object)player == (Object)null) && TryGetPrefab(out var prefab)) { Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory != null) { inventory.AddItem(prefab, amount); } } } } } namespace ThorsHeartyMeals.Config { internal static class ModSettings { internal static ConfigEntry<int> EatsRequired; internal static ConfigEntry<bool> ResetProgressOnDeath; internal static ConfigEntry<bool> ShowProgressMessages; internal static ConfigEntry<string> BukeperriesItemNames; internal static ConfigEntry<bool> BukeperriesClearsEatProgress; internal static ConfigEntry<bool> GrantBukeperriesOnUnlock; internal static void Bind(ConfigFile config) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown EatsRequired = config.Bind<int>("General", "EatsRequired", 10, new ConfigDescription("How many times the same food must be eaten before its buff becomes permanent.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 1000), Array.Empty<object>())); ResetProgressOnDeath = config.Bind<bool>("General", "ResetProgressOnDeath", true, "When true, dying also resets every food's eat-count. Permanent buffs are always removed on death regardless of this setting."); ShowProgressMessages = config.Bind<bool>("General", "ShowProgressMessages", true, "Show a chat message with the current progress each time you eat a food that is not yet permanent."); BukeperriesItemNames = config.Bind<string>("Bukeperries", "ItemNames", "Pukeberries,Bukeperries", "Comma-separated list of item prefab names that count as a Bukeperries. The vanilla item's internal prefab id is \"Pukeberries\"; \"Bukeperries\" is its display name."); BukeperriesClearsEatProgress = config.Bind<bool>("Bukeperries", "ClearsEatProgress", true, "When true, eating a Bukeperries also resets every food's eat-count, not just the permanent buffs."); GrantBukeperriesOnUnlock = config.Bind<bool>("Bukeperries", "GrantOnUnlock", false, "Give the player one Bukeperries in their inventory whenever a food becomes permanent."); } } }