Decompiled source of ThorsHeartyMeals v1.0.0

BepInEx/plugins/ThorsHeartyMeals/ThorsHeartyMeals.dll

Decompiled 5 days ago
using 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.");
		}
	}
}