Decompiled source of PermanentMeals v0.4.1

BepInEx/plugins/PermanentMeals/PermanentMeals.dll

Decompiled 5 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using AzuEPI;
using AzuEPI.Core.InventoryHandlers;
using BepInEx;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("PermanentMeals")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.4.1.0")]
[assembly: AssemblyInformationalVersion("0.4.1")]
[assembly: AssemblyProduct("PermanentMeals")]
[assembly: AssemblyTitle("PermanentMeals")]
[assembly: AssemblyVersion("0.4.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 PermanentMeals
{
	[BepInPlugin("local.permanentmeals", "Permanent Meals", "0.4.1")]
	[BepInDependency("Azumatt.AzuExtendedPlayerInventory", "2.4.14")]
	public sealed class Plugin : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(InventoryGui), "OnSelectedItem")]
		private static class SelectMealPatch
		{
			[HarmonyPriority(800)]
			[HarmonyBefore(new string[] { "Azumatt.AzuExtendedPlayerInventory" })]
			private static bool Prefix(InventoryGui __instance, InventoryGrid grid, Vector2i pos, Modifier mod)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_000e: Unknown result type (might be due to invalid IL or missing references)
				int num = MealAt(grid, pos);
				if (num < 0)
				{
					return true;
				}
				if ((int)mod == 0)
				{
					PlaceMeal(__instance, num);
				}
				return false;
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "OnRightClickItem")]
		private static class ClearMealPatch
		{
			[HarmonyPriority(800)]
			[HarmonyBefore(new string[] { "Azumatt.AzuExtendedPlayerInventory" })]
			private static bool Prefix(InventoryGui __instance, InventoryGrid grid, Vector2i pos)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				int num = MealAt(grid, pos);
				if (num < 0)
				{
					return true;
				}
				if (DragItem.Invoke(__instance) != null)
				{
					return false;
				}
				ClearSingleMeal(Player.m_localPlayer, num);
				return false;
			}
		}

		[HarmonyPatch(typeof(InventoryGrid), "DropItem")]
		private static class DropMealPatch
		{
			[HarmonyPriority(800)]
			[HarmonyBefore(new string[] { "Azumatt.AzuExtendedPlayerInventory" })]
			private static bool Prefix(InventoryGrid __instance, Vector2i pos, ref bool __result)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				if (MealAt(__instance, pos) < 0)
				{
					return true;
				}
				__result = false;
				return false;
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "UpdateInventory")]
		private static class DrawMealsPatch
		{
			[HarmonyPriority(0)]
			[HarmonyAfter(new string[] { "Azumatt.AzuExtendedPlayerInventory" })]
			private static void Postfix(InventoryGui __instance)
			{
				//IL_019b: Unknown result type (might be due to invalid IL or missing references)
				Player localPlayer = Player.m_localPlayer;
				if (!Object.op_Implicit((Object)(object)localPlayer) || !Object.op_Implicit((Object)(object)__instance.m_playerGrid))
				{
					return;
				}
				bool flag = Ensure(localPlayer);
				List<InventoryElement> list = Elements.Invoke(__instance.m_playerGrid);
				string[] originalSlotNames = API.GetSlots().OriginalSlotNames;
				int num = BaseSlotIndex(((Humanoid)localPlayer).GetInventory());
				for (int i = 0; i < 3; i++)
				{
					int num2 = Array.IndexOf(originalSlotNames, SlotNames[i]);
					if (num2 >= 0 && num + num2 >= 0 && num + num2 < list.Count)
					{
						InventoryElement val = list[num + num2];
						Transform val2 = ((Component)val).transform.Find("EPI_InvalidOverlay");
						ItemData val3 = DragItem.Invoke(__instance);
						bool active = val3 != null && (DragInventory.Invoke(__instance) != ((Humanoid)localPlayer).GetInventory() || DragAmount.Invoke(__instance) != val3.m_stack || !CanDeposit(localPlayer, val3, i));
						if (Object.op_Implicit((Object)(object)val2))
						{
							((Component)val2).gameObject.SetActive(active);
						}
						Meal meal = (flag ? state.slots[i] : null);
						GameObject val4 = ((meal == null || !Object.op_Implicit((Object)(object)ObjectDB.instance)) ? null : ObjectDB.instance.GetItemPrefab(meal.prefab));
						ItemDrop val5 = (Object.op_Implicit((Object)(object)val4) ? val4.GetComponent<ItemDrop>() : null);
						((Behaviour)val.m_icon).enabled = (Object)(object)val5 != (Object)null;
						if (Object.op_Implicit((Object)(object)val5))
						{
							val.m_icon.sprite = val5.m_itemData.GetIcon();
							((Graphic)val.m_icon).color = Color.white;
						}
						((Behaviour)val.m_amount).enabled = meal != null;
						val.m_amount.text = ((meal == null) ? "" : meal.count.ToString());
						((Behaviour)val.m_equiped).enabled = meal != null;
						val.m_tooltip.m_topic = SlotNames[i] + ((meal != null) ? " — Locked" : "");
						val.m_tooltip.m_text = ((!flag) ? "Unreadable meal save. Original data preserved." : ((meal == null) ? "Pick up a full food stack and place it here.\nEat Bukeperries to clear all meals." : ((Object.op_Implicit((Object)(object)val5) ? Localization.instance.Localize(val5.m_itemData.m_shared.m_name) : (meal.prefab + " (unavailable)")) + "\nRight-click to clear. Costs 1 Bukeperry.\nStored food is lost.\nEating Bukeperries clears all meals.")));
					}
				}
			}
		}

		[HarmonyPatch(typeof(Player), "Load")]
		private static class LoadPatch
		{
			private static void Postfix(Player __instance)
			{
				if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer)
				{
					Read(__instance);
				}
			}
		}

		[HarmonyPatch(typeof(Player), "UpdateFood")]
		private static class TickPatch
		{
			private static void Prefix(Player __instance)
			{
				Refresh(__instance, beforeTick: true);
			}

			private static void Postfix(Player __instance)
			{
				Refresh(__instance, beforeTick: false);
			}
		}

		[HarmonyPatch(typeof(Player), "GetTotalFoodValue")]
		private static class TotalsPatch
		{
			private static void Prefix(Player __instance)
			{
				Refresh(__instance, beforeTick: false);
			}
		}

		[HarmonyPatch(typeof(Player), "CanEat")]
		private static class CanEatPatch
		{
			private static bool Prefix(Player __instance, ItemData item, ref bool __result)
			{
				if (!Ensure(__instance))
				{
					return true;
				}
				Refresh(__instance, beforeTick: false);
				int num = state.slots.Count((Meal m) => m != null && !__instance.GetFoods().Any((Food f) => f.m_name == m.prefab));
				if (__instance.GetFoods().Any((Food f) => Stored(f) && f.m_item.m_shared.m_name == item.m_shared.m_name) || (num > 0 && __instance.GetFoods().Count + num >= 3))
				{
					__result = false;
					return false;
				}
				return true;
			}
		}

		[HarmonyPatch(typeof(Player), "ConsumeItem")]
		private static class BerriesPatch
		{
			private static void Postfix(Player __instance, ItemData item, bool __result)
			{
				if (__result && !((Object)(object)item.m_dropPrefab == (Object)null) && !(((Object)item.m_dropPrefab).name != "Pukeberries") && Ensure(__instance))
				{
					__instance.GetFoods().RemoveAll(Stored);
					state = new SavedMeals();
					Persist(__instance);
				}
			}
		}

		private const string AzuId = "Azumatt.AzuExtendedPlayerInventory";

		private static readonly string[] SlotNames = new string[3] { "Permanent Meal 1", "Permanent Meal 2", "Permanent Meal 3" };

		private static readonly FieldRef<InventoryGui, ItemData> DragItem = AccessTools.FieldRefAccess<InventoryGui, ItemData>("m_dragItem");

		private static readonly FieldRef<InventoryGui, Inventory> DragInventory = AccessTools.FieldRefAccess<InventoryGui, Inventory>("m_dragInventory");

		private static readonly FieldRef<InventoryGui, int> DragAmount = AccessTools.FieldRefAccess<InventoryGui, int>("m_dragAmount");

		private static readonly FieldRef<InventoryGrid, List<InventoryElement>> Elements = AccessTools.FieldRefAccess<InventoryGrid, List<InventoryElement>>("m_elements");

		private static readonly MethodInfo ClearDrag = AccessTools.Method(typeof(InventoryGui), "SetupDragItem", (Type[])null, (Type[])null);

		public const string Id = "local.permanentmeals";

		private const string SaveKey = "local.permanentmeals.v1";

		private static Plugin instance;

		private static Player owner;

		private static SavedMeals state = new SavedMeals();

		private static bool invalidSave;

		private Harmony harmony;

		private void RegisterMealSlots()
		{
			string[] slotNames = SlotNames;
			foreach (string text in slotNames)
			{
				if (!API.AddSlot(text, (Func<Player, ItemData>)((Player _) => (ItemData?)null), (Func<ItemData, bool>)((ItemData _) => false), -1))
				{
					throw new InvalidOperationException("Azu refused slot: " + text);
				}
			}
		}

		private static int BaseSlotIndex(Inventory inventory)
		{
			return Layout.GetBaseSlotIndex(inventory);
		}

		private static int MealAt(InventoryGrid grid, Vector2i position)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer) || !Object.op_Implicit((Object)(object)InventoryGui.instance) || (Object)(object)grid != (Object)(object)InventoryGui.instance.m_playerGrid)
			{
				return -1;
			}
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			int num = position.y * inventory.GetWidth() + position.x - BaseSlotIndex(inventory);
			string[] originalSlotNames = API.GetSlots().OriginalSlotNames;
			if (num >= 0 && num < originalSlotNames.Length)
			{
				return Array.IndexOf(SlotNames, originalSlotNames[num]);
			}
			return -1;
		}

		private static void PlaceMeal(InventoryGui gui, int slot)
		{
			Player localPlayer = Player.m_localPlayer;
			if (Ensure(localPlayer) && state.slots[slot] == null)
			{
				ItemData val = DragItem.Invoke(gui);
				if (val != null && DragInventory.Invoke(gui) == ((Humanoid)localPlayer).GetInventory() && DragAmount.Invoke(gui) == val.m_stack && instance.Deposit(localPlayer, val, slot))
				{
					ClearDrag.Invoke(gui, new object[3] { null, null, 1 });
				}
			}
		}

		private void Awake()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			instance = this;
			harmony = new Harmony("local.permanentmeals");
			harmony.PatchAll();
			RegisterMealSlots();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Permanent Meals 0.4.1 loaded with three Azu equipment slots.");
		}

		private void OnDestroy()
		{
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
		}

		private static bool Ensure(Player player)
		{
			if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return false;
			}
			if ((Object)(object)owner != (Object)(object)player)
			{
				Read(player);
			}
			return !invalidSave;
		}

		private static void Read(Player player)
		{
			owner = player;
			state = new SavedMeals();
			invalidSave = false;
			if (!player.m_customData.TryGetValue("local.permanentmeals.v1", out var value))
			{
				return;
			}
			try
			{
				state = MealCodec.Decode(value);
			}
			catch (Exception ex)
			{
				invalidSave = true;
				((BaseUnityPlugin)instance).Logger.LogError((object)("Meal data could not be read; original data preserved. " + ex));
			}
		}

		private static void Persist(Player player)
		{
			player.m_customData["local.permanentmeals.v1"] = MealCodec.Encode(state);
		}

		private static bool Stored(Food food)
		{
			return state.slots.Any((Meal m) => m != null && m.prefab == food.m_name);
		}

		private static void Refresh(Player player, bool beforeTick)
		{
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Expected O, but got Unknown
			if (!Ensure(player) || ((Character)player).IsDead() || (Object)(object)ObjectDB.instance == (Object)null)
			{
				return;
			}
			List<Food> foods = player.GetFoods();
			Meal[] slots = state.slots;
			foreach (Meal meal in slots)
			{
				if (meal == null)
				{
					continue;
				}
				GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(meal.prefab);
				ItemDrop val = (((Object)(object)itemPrefab == (Object)null) ? null : itemPrefab.GetComponent<ItemDrop>());
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Food val2 = ((IEnumerable<Food>)foods).FirstOrDefault((Func<Food, bool>)((Food f) => f.m_name == meal.prefab));
				if (val2 == null)
				{
					if (foods.Count >= 3)
					{
						continue;
					}
					val2 = new Food
					{
						m_name = meal.prefab,
						m_item = val.m_itemData.Clone()
					};
					foods.Add(val2);
				}
				SharedData shared = val2.m_item.m_shared;
				val2.m_time = shared.m_foodBurnTime + (beforeTick ? 1f : 0f);
				val2.m_health = shared.m_food;
				val2.m_stamina = shared.m_foodStamina;
				val2.m_eitr = shared.m_foodEitr;
			}
		}

		private static bool CanDeposit(Player player, ItemData item, int selected)
		{
			if (item == null || selected < 0 || selected >= 3 || !Ensure(player) || ((Character)player).IsDead() || ((Character)player).InCutscene() || ((Character)player).GetSEMan().HaveStatusEffect(StringExtensionMethods.GetStableHashCode("Puke")))
			{
				return false;
			}
			if (!((Humanoid)player).GetInventory().ContainsItem(item) || (Object)(object)item.m_dropPrefab == (Object)null)
			{
				return false;
			}
			bool duplicate = player.GetFoods().Any((Food f) => f.m_item.m_shared.m_name == item.m_shared.m_name) || state.slots.Any((Meal m) => m != null && m.prefab == ((Object)item.m_dropPrefab).name);
			int num = state.slots.Count((Meal m) => m != null && !player.GetFoods().Any((Food f) => f.m_name == m.prefab));
			return MealRules.CanStore(selected, state.slots[selected] != null, item.m_stack, item.m_shared.m_maxStackSize, item.m_shared.m_food > 0f && item.m_shared.m_foodBurnTime > 0f, duplicate, player.GetFoods().Count + num);
		}

		private bool Deposit(Player player, ItemData item, int selected)
		{
			if (!CanDeposit(player, item, selected))
			{
				return false;
			}
			Meal meal = new Meal
			{
				prefab = ((Object)item.m_dropPrefab).name,
				count = item.m_stack
			};
			state.slots[selected] = meal;
			string value;
			try
			{
				value = MealCodec.Encode(state);
			}
			catch
			{
				state.slots[selected] = null;
				throw;
			}
			if (!((Humanoid)player).GetInventory().RemoveItem(item))
			{
				state.slots[selected] = null;
				return false;
			}
			player.m_customData["local.permanentmeals.v1"] = value;
			Refresh(player, beforeTick: false);
			return true;
		}

		private static void ClearSingleMeal(Player player, int slot)
		{
			if (!Ensure(player) || ((Character)player).IsDead() || ((Character)player).InCutscene() || slot < 0 || slot >= 3)
			{
				return;
			}
			Meal meal = state.slots[slot];
			if (meal == null)
			{
				return;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			ItemData val = ((IEnumerable<ItemData>)inventory.GetAllItems()).FirstOrDefault((Func<ItemData, bool>)((ItemData i) => i.m_stack > 0 && Object.op_Implicit((Object)(object)i.m_dropPrefab) && ((Object)i.m_dropPrefab).name == "Pukeberries"));
			if (val == null)
			{
				((Character)player).Message((MessageType)2, "You need 1 Bukeperry in your inventory to clear this meal.", 0, (Sprite)null, false);
				return;
			}
			SavedMeals meals = MealRules.ClearSlot(state, slot);
			string value = MealCodec.Encode(meals);
			if (inventory.RemoveItem(val, 1))
			{
				state = meals;
				player.m_customData["local.permanentmeals.v1"] = value;
				player.GetFoods().RemoveAll((Food f) => f.m_name == meal.prefab);
			}
		}
	}
	public sealed class Meal
	{
		public string prefab;

		public int count;
	}
	public sealed class SavedMeals
	{
		public Meal[] slots = new Meal[3];
	}
	public static class MealCodec
	{
		public static string Encode(SavedMeals meals)
		{
			return "1|" + string.Join("|", meals.slots.Select((Meal m) => (m != null) ? (Convert.ToBase64String(Encoding.UTF8.GetBytes(m.prefab)) + ":" + m.count.ToString(CultureInfo.InvariantCulture)) : "-"));
		}

		public static SavedMeals Decode(string value)
		{
			string[] array = value.Split('|');
			if (array.Length != 4 || array[0] != "1")
			{
				throw new FormatException("Unsupported meal save format.");
			}
			SavedMeals savedMeals = new SavedMeals();
			for (int i = 0; i < 3; i++)
			{
				if (!(array[i + 1] == "-"))
				{
					string[] array2 = array[i + 1].Split(':');
					if (array2.Length != 2 || !int.TryParse(array2[1], NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result < 1)
					{
						throw new FormatException("Invalid meal count.");
					}
					string prefab = Encoding.UTF8.GetString(Convert.FromBase64String(array2[0]));
					if (string.IsNullOrWhiteSpace(prefab) || savedMeals.slots.Any((Meal m) => m != null && m.prefab == prefab))
					{
						throw new FormatException("Invalid or duplicate food prefab.");
					}
					savedMeals.slots[i] = new Meal
					{
						prefab = prefab,
						count = result
					};
				}
			}
			return savedMeals;
		}
	}
	public static class MealRules
	{
		public const int SlotCount = 3;

		public static SavedMeals ClearSlot(SavedMeals current, int slot)
		{
			if (slot < 0 || slot >= 3)
			{
				throw new ArgumentOutOfRangeException("slot");
			}
			SavedMeals obj = new SavedMeals
			{
				slots = (Meal[])current.slots.Clone()
			};
			obj.slots[slot] = null;
			return obj;
		}

		public static bool CanStore(int slot, bool occupied, int stack, int maxStack, bool isFood, bool duplicate, int activeFoods)
		{
			if (slot >= 0 && slot < 3 && !occupied && isFood && maxStack > 0 && stack == maxStack && !duplicate)
			{
				return activeFoods < 3;
			}
			return false;
		}
	}
}