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 FoodStackPlus v1.1.1
FoodStackPlus.dll
Decompiled 3 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyVersion("0.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 FoodStackPlus { [BepInPlugin("saymonn.FoodStackPlus", "FoodStackPlus", "1.1.1")] public class Plugin : BaseUnityPlugin { public const string Guid = "saymonn.FoodStackPlus"; public static ManualLogSource Log; private void Awake() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; ModConfig.Bind(((BaseUnityPlugin)this).Config); new Harmony("saymonn.FoodStackPlus").PatchAll(); } } public static class ModConfig { private const int VanillaSlots = 3; private static ConfigEntry<bool> modEnabled; private static ConfigEntry<bool> slotsEnabled; private static ConfigEntry<int> maxFoodSlots; private static ConfigEntry<bool> sameFoodEnabled; private static ConfigEntry<int> maxSameFood; private static ConfigEntry<bool> refreshWhenFull; private static ConfigEntry<float> durationMultiplier; public static bool Enabled => modEnabled.Value; public static int MaxSlots { get { if (!slotsEnabled.Value) { return 3; } return maxFoodSlots.Value; } } public static int MaxSameFood { get { if (!sameFoodEnabled.Value) { return 1; } return maxSameFood.Value; } } public static bool RefreshWhenFull => refreshWhenFull.Value; public static float DurationMultiplier { get { if (!modEnabled.Value) { return 1f; } return durationMultiplier.Value; } } public static void Bind(ConfigFile cfg) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected O, but got Unknown //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Expected O, but got Unknown modEnabled = cfg.Bind<bool>("General", "Enabled", true, "Master switch. When off, the game's normal food rules apply (3 slots, no repeats)."); slotsEnabled = cfg.Bind<bool>("FoodSlots", "Enabled", true, "Use a custom number of active food slots instead of the vanilla 3."); maxFoodSlots = cfg.Bind<int>("FoodSlots", "MaxFoodSlots", 6, new ConfigDescription("How many foods can be active at once (vanilla: 3). Extra slots appear in the HUD.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 16), Array.Empty<object>())); sameFoodEnabled = cfg.Bind<bool>("SameFood", "Enabled", true, "Allow eating the same food several times at once. Each serving takes its own slot and has its own timer."); maxSameFood = cfg.Bind<int>("SameFood", "MaxServingsOfSameFood", 3, new ConfigDescription("How many servings of one food can be active at once (vanilla: 1). Also limited by MaxFoodSlots.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 16), Array.Empty<object>())); refreshWhenFull = cfg.Bind<bool>("Eating", "AllowRefreshWhenFull", true, "When no free slot or serving is left, allow eating again to refresh the most depleted serving (vanilla rule: only when its timer is below half). When off, you cannot eat until a food runs out."); durationMultiplier = cfg.Bind<float>("Duration", "DurationMultiplier", 1f, new ConfigDescription("Multiplies how long every food lasts. 1 = vanilla, 1.5 = a 20 minute food lasts 30, 3 = 60 minutes. Applies to all foods (health, stamina and Eitr bonuses stay the same). Tooltips show the new duration.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 20f), Array.Empty<object>())); modEnabled.SettingChanged += delegate { FoodDurationScale.OnConfigChanged(); }; durationMultiplier.SettingChanged += delegate { FoodDurationScale.OnConfigChanged(); }; } } public static class FoodDurationScale { [HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[] { typeof(ItemData), typeof(int), typeof(bool), typeof(float), typeof(int), typeof(bool) })] private static class TooltipPatch { private static void Prefix(ItemData item) { Normalize(item); } } [HarmonyPatch(typeof(ObjectDB), "Awake")] private static class AwakePatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(ObjectDB __instance) { Apply(__instance); } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] private static class CopyPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(ObjectDB __instance) { Apply(__instance); } } private static readonly Dictionary<string, float> original = new Dictionary<string, float>(); private static float applied = 1f; public static void Apply(ObjectDB db) { if ((Object)(object)db == (Object)null || db.m_items == null) { return; } float durationMultiplier = ModConfig.DurationMultiplier; foreach (GameObject item in db.m_items) { if ((Object)(object)item == (Object)null) { continue; } ItemDrop component = item.GetComponent<ItemDrop>(); if ((Object)(object)component == (Object)null || component.m_itemData == null || component.m_itemData.m_shared == null) { continue; } SharedData shared = component.m_itemData.m_shared; if (!(shared.m_food <= 0f) || !(shared.m_foodStamina <= 0f) || !(shared.m_foodEitr <= 0f)) { if (!original.TryGetValue(((Object)item).name, out var value)) { value = shared.m_foodBurnTime; original[((Object)item).name] = value; } if (value > 0f) { shared.m_foodBurnTime = value * durationMultiplier; } } } applied = durationMultiplier; } public static void Normalize(ItemData item) { if (item != null && item.m_shared != null && !((Object)(object)item.m_dropPrefab == (Object)null)) { SharedData shared = item.m_shared; if ((!(shared.m_food <= 0f) || !(shared.m_foodStamina <= 0f) || !(shared.m_foodEitr <= 0f)) && original.TryGetValue(((Object)item.m_dropPrefab).name, out var value) && !(value <= 0f)) { shared.m_foodBurnTime = value * ModConfig.DurationMultiplier; } } } public static void OnConfigChanged() { float num = applied; Apply(ObjectDB.instance); float num2 = applied / num; if (num2 == 1f || (Object)(object)Player.m_localPlayer == (Object)null) { return; } foreach (Food food in Player.m_localPlayer.GetFoods()) { Normalize(food.m_item); food.m_time *= num2; } } } public enum EatAction { Deny, AddNew, Refresh } public static class FoodRules { public static EatAction Decide(Player player, ItemData item, out Food target, out bool sameFoodActive) { target = null; List<Food> foods = player.GetFoods(); string name = item.m_shared.m_name; int num = 0; foreach (Food item2 in foods) { if (item2.m_item.m_shared.m_name == name) { num++; } } sameFoodActive = num > 0; int maxSameFood = ModConfig.MaxSameFood; if (foods.Count < ModConfig.MaxSlots && num < maxSameFood) { return EatAction.AddNew; } if (!ModConfig.RefreshWhenFull) { return EatAction.Deny; } target = MostDepleted(foods, (Food f) => f.m_item.m_shared.m_name == name); if (target == null && num < maxSameFood) { target = MostDepleted(foods, (Food f) => true); } if (target == null) { return EatAction.Deny; } return EatAction.Refresh; } private static Food MostDepleted(List<Food> foods, Func<Food, bool> filter) { Food val = null; foreach (Food food in foods) { if (filter(food) && food.CanEatAgain() && (val == null || food.m_time < val.m_time)) { val = food; } } return val; } public static void Fill(Food food, ItemData item) { FoodDurationScale.Normalize(item); food.m_name = (Object.op_Implicit((Object)(object)item.m_dropPrefab) ? ((Object)item.m_dropPrefab).name : item.m_shared.m_name); food.m_item = item; food.m_time = item.m_shared.m_foodBurnTime; food.m_health = item.m_shared.m_food; food.m_stamina = item.m_shared.m_foodStamina; food.m_eitr = item.m_shared.m_foodEitr; } } internal static class HudSlots { [HarmonyPatch(typeof(Hud), "UpdateFood")] private static class UpdateFoodPatch { private static void Prefix(Hud __instance) { if (!ModConfig.Enabled) { return; } try { Ensure(__instance); } catch (Exception ex) { Fail("Could not add HUD food slots: " + ex); } } private static void Postfix(Hud __instance, Player player) { if (!ModConfig.Enabled || failed || (Object)(object)builtFor != (Object)(object)__instance) { return; } int num = Math.Max(ModConfig.MaxSlots, player.GetFoods().Count); for (int i = 0; i < wrappers.Count; i++) { if (Object.op_Implicit((Object)(object)wrappers[i])) { wrappers[i].SetActive(wrapperSlot[i] < num); } } } } private static Hud builtFor; private static int baseCount; private static bool failed; private static readonly List<GameObject> wrappers = new List<GameObject>(); private static readonly List<int> wrapperSlot = new List<int>(); private static void Fail(string message) { failed = true; Plugin.Log.LogWarning((object)message); } private static void Ensure(Hud hud) { //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_02d3: Unknown result type (might be due to invalid IL or missing references) //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)builtFor != (Object)(object)hud) { builtFor = hud; baseCount = hud.m_foodBars.Length; wrappers.Clear(); wrapperSlot.Clear(); failed = false; } int maxSlots = ModConfig.MaxSlots; int num = hud.m_foodBars.Length; if (failed || num >= maxSlots) { return; } if (num < 2) { Fail("Food HUD has fewer than 2 slots, cannot clone."); return; } Transform[] array = Elements(hud, num - 1); Transform[] array2 = Elements(hud, num - 2); List<Transform> list = new List<Transform>(); List<Transform> list2 = new List<Transform>(); int[] array3 = new int[3]; List<int>[] array4 = new List<int>[3]; for (int i = 0; i < 3; i++) { Transform val = Lca(array[i], array2[i]); if (!Object.op_Implicit((Object)(object)val) || (Object)(object)val == (Object)(object)array[i] || (Object)(object)val == (Object)(object)array2[i]) { Fail("Food HUD slots share elements, cannot clone."); return; } Transform val2 = ChildOf(val, array[i]); Transform item = ChildOf(val, array2[i]); int num2 = list.IndexOf(val2); if (num2 < 0) { list.Add(val2); list2.Add(item); num2 = list.Count - 1; } array3[i] = num2; array4[i] = PathFrom(val2, array[i]); } bool[] array5 = new bool[list.Count]; Vector2[] array6 = (Vector2[])(object)new Vector2[list.Count]; for (int j = 0; j < list.Count; j++) { array5[j] = Array.IndexOf(array, list[j]) < 0; Transform obj = list[j]; RectTransform val3 = (RectTransform)(object)((obj is RectTransform) ? obj : null); Transform obj2 = list2[j]; RectTransform val4 = (RectTransform)(object)((obj2 is RectTransform) ? obj2 : null); array6[j] = ((Object.op_Implicit((Object)(object)val3) && Object.op_Implicit((Object)(object)val4)) ? (val3.anchoredPosition - val4.anchoredPosition) : Vector2.zero); if (array6[j] == Vector2.zero) { array6[j] = new Vector2(0f, 46f); } } Array.Resize(ref hud.m_foodBars, maxSlots); Array.Resize(ref hud.m_foodIcons, maxSlots); Array.Resize(ref hud.m_foodTime, maxSlots); List<Transform> list3 = list; for (int k = num; k < maxSlots; k++) { List<Transform> list4 = new List<Transform>(); for (int l = 0; l < list3.Count; l++) { GameObject val5 = Object.Instantiate<GameObject>(((Component)list3[l]).gameObject, list3[l].parent, false); ((Object)val5).name = "FSP_food" + k + "_" + l; Transform transform = val5.transform; RectTransform val6 = (RectTransform)(object)((transform is RectTransform) ? transform : null); Transform obj3 = list3[l]; RectTransform val7 = (RectTransform)(object)((obj3 is RectTransform) ? obj3 : null); if (Object.op_Implicit((Object)(object)val6) && Object.op_Implicit((Object)(object)val7)) { val6.anchoredPosition = val7.anchoredPosition + array6[l]; } if (array5[l]) { val5.SetActive(k < ModConfig.MaxSlots); wrappers.Add(val5); wrapperSlot.Add(k); } list4.Add(val5.transform); } hud.m_foodBars[k] = ((Component)Follow(list4[array3[0]], array4[0])).GetComponent<Image>(); hud.m_foodIcons[k] = ((Component)Follow(list4[array3[1]], array4[1])).GetComponent<Image>(); hud.m_foodTime[k] = ((Component)Follow(list4[array3[2]], array4[2])).GetComponent<TMP_Text>(); list3 = list4; } Plugin.Log.LogInfo((object)("Food HUD: " + num + " -> " + maxSlots + " slots (" + list.Count + " cloned object(s) per slot, " + wrappers.Count + " wrapper(s) total).")); } private static Transform[] Elements(Hud hud, int k) { return (Transform[])(object)new Transform[3] { ((Component)hud.m_foodBars[k]).transform, ((Component)hud.m_foodIcons[k]).transform, hud.m_foodTime[k].transform }; } private static Transform Lca(Transform x, Transform y) { Transform val = x; while (Object.op_Implicit((Object)(object)val)) { if (y.IsChildOf(val)) { return val; } val = val.parent; } return null; } private static Transform ChildOf(Transform parent, Transform t) { while ((Object)(object)t.parent != (Object)(object)parent) { t = t.parent; } return t; } private static List<int> PathFrom(Transform root, Transform child) { List<int> list = new List<int>(); Transform val = child; while (Object.op_Implicit((Object)(object)val) && (Object)(object)val != (Object)(object)root) { list.Add(val.GetSiblingIndex()); val = val.parent; } list.Reverse(); return list; } private static Transform Follow(Transform root, List<int> path) { Transform val = root; foreach (int item in path) { val = val.GetChild(item); } return val; } } internal static class PlayerPatches { [HarmonyPatch(typeof(Player), "CanEat")] private static class CanEatPatch { private static bool Prefix(Player __instance, ItemData item, bool showMessages, ref bool __result) { if (!ModConfig.Enabled) { return true; } Food target; bool sameFoodActive; EatAction eatAction = FoodRules.Decide(__instance, item, out target, out sameFoodActive); __result = eatAction != EatAction.Deny; if (!__result && showMessages) { if (sameFoodActive) { ((Character)__instance).Message((MessageType)2, Localization.instance.Localize("$msg_nomore", new string[1] { item.m_shared.m_name }), 0, (Sprite)null, false); } else { ((Character)__instance).Message((MessageType)2, "$msg_isfull", 0, (Sprite)null, false); } } return false; } } [HarmonyPatch(typeof(Player), "EatFood")] private static class EatFoodPatch { private static bool Prefix(Player __instance, ItemData item, ref bool __result) { //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected O, but got Unknown if (!ModConfig.Enabled) { return true; } Food target; bool sameFoodActive; EatAction eatAction = FoodRules.Decide(__instance, item, out target, out sameFoodActive); if (eatAction == EatAction.Deny) { __result = false; return false; } string text = ""; if (item.m_shared.m_food > 0f) { text = text + " +" + item.m_shared.m_food + " $item_food_health "; } if (item.m_shared.m_foodStamina > 0f) { text = text + " +" + item.m_shared.m_foodStamina + " $item_food_stamina "; } if (item.m_shared.m_foodEitr > 0f) { text = text + " +" + item.m_shared.m_foodEitr + " $item_food_eitr "; } ((Character)__instance).Message((MessageType)2, text, 0, (Sprite)null, false); if (eatAction == EatAction.AddNew) { Food val = new Food(); FoodRules.Fill(val, item); __instance.GetFoods().Add(val); } else { FoodRules.Fill(target, item); } Game.instance.IncrementPlayerStat((PlayerStatType)49, 1f, false); Game.instance.GetPlayerProfile().IncrementStatFoodEaten(item.m_shared.m_name, 1f, item.m_cheated); UpdateFood.Invoke(__instance, new object[2] { 0f, true }); __result = true; return false; } } private static readonly MethodInfo UpdateFood = AccessTools.Method(typeof(Player), "UpdateFood", (Type[])null, (Type[])null); } }