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 PermanentMeals v0.4.1
BepInEx/plugins/PermanentMeals/PermanentMeals.dll
Decompiled 5 days agousing 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; } } }