Decompiled source of FoodStackPlus v1.1.1

FoodStackPlus.dll

Decompiled 3 hours ago
using 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);
	}
}