Decompiled source of BerrySlot v0.1.2

BepInEx/plugins/BerrySlot/BerrySlot.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
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 UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("BerrySlot")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.2.0")]
[assembly: AssemblyInformationalVersion("0.1.2")]
[assembly: AssemblyProduct("BerrySlot")]
[assembly: AssemblyTitle("BerrySlot")]
[assembly: AssemblyVersion("0.1.2.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 BerrySlot
{
	public sealed class BerryHud : MonoBehaviour
	{
		private Image? _icon;

		private TMP_Text? _timer;

		private readonly List<GameObject> _roots = new List<GameObject>();

		private bool _attempted;

		private static readonly ManualLogSource Log = Logger.CreateLogSource("Berry Slot HUD");

		internal void Render(Hud hud, Food? berry)
		{
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			if (!_attempted)
			{
				_attempted = true;
				try
				{
					Build(hud);
				}
				catch (Exception ex)
				{
					foreach (GameObject root in _roots)
					{
						Object.Destroy((Object)(object)root);
					}
					_roots.Clear();
					_icon = null;
					_timer = null;
					Log.LogError((object)("Could not clone the native food HUD. " + ex));
				}
			}
			if ((Object)(object)_icon == (Object)null || (Object)(object)_timer == (Object)null)
			{
				return;
			}
			foreach (GameObject root2 in _roots)
			{
				root2.SetActive(berry != null);
			}
			if (berry != null)
			{
				((Component)_icon).gameObject.SetActive(true);
				((Component)_timer).gameObject.SetActive(true);
				_icon.sprite = berry.m_item.GetIcon();
				((Graphic)_icon).color = (Color)(berry.CanEatAgain() ? new Color(1f, 1f, 1f, 0.7f + Mathf.Sin(Time.time * 5f) * 0.3f) : Color.white);
				float num = berry.m_time / Game.m_foodRate;
				_timer.text = ((num >= 60f) ? (Mathf.CeilToInt(num / 60f) + "m") : (Mathf.FloorToInt(num) + "s"));
				((Graphic)_timer).color = (Color)((num >= 60f) ? Color.white : new Color(1f, 1f, 1f, 0.4f + Mathf.Sin(Time.time * 10f) * 0.6f));
			}
		}

		private void Build(Hud hud)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			if (hud.m_foodIcons.Length != 3 || hud.m_foodTime.Length != 3 || hud.m_foodBars.Length != 3)
			{
				throw new InvalidOperationException("Expected a three-slot vanilla food HUD. Disable extra-slot HUD mods.");
			}
			int num = 0;
			for (int i = 1; i < 3; i++)
			{
				if (((Component)hud.m_foodIcons[i]).transform.position.y > ((Component)hud.m_foodIcons[num]).transform.position.y)
				{
					num = i;
				}
			}
			Image val = hud.m_foodIcons[num];
			TMP_Text val2 = hud.m_foodTime[num];
			Transform val3 = ((Component)val).transform;
			while ((Object)(object)val3 != (Object)null && !val2.transform.IsChildOf(val3))
			{
				val3 = val3.parent;
			}
			if ((Object)(object)val3 == (Object)null)
			{
				throw new InvalidOperationException("Food icon and timer have no common parent.");
			}
			bool flag = false;
			for (int j = 0; j < 3; j++)
			{
				if (j != num && ((Component)hud.m_foodIcons[j]).transform.IsChildOf(val3))
				{
					flag = true;
				}
			}
			if (flag)
			{
				throw new InvalidOperationException("The food HUD was replaced by another mod; no isolated native slot template exists.");
			}
			float num2 = float.NegativeInfinity;
			for (int k = 0; k < 3; k++)
			{
				if (k != num)
				{
					num2 = Mathf.Max(num2, ((Component)hud.m_foodIcons[k]).transform.position.y);
				}
			}
			float num3 = ((Component)val).transform.position.y - num2;
			if (num3 <= 0f)
			{
				throw new InvalidOperationException("Food slots must have a vertical layout.");
			}
			Vector3 val4 = ((Component)val).transform.parent.TransformVector(new Vector3(Plugin.OffsetX.Value, Plugin.OffsetY.Value, 0f));
			Vector3 shift = new Vector3(0f, num3, 0f) + val4;
			GameObject val5 = Clone(((Component)val3).gameObject, shift);
			_icon = Counterpart<Image>(((Component)val).transform, val3, val5.transform);
			_timer = Counterpart<TMP_Text>(val2.transform, val3, val5.transform);
			if ((Object)(object)_icon == (Object)null || (Object)(object)_timer == (Object)null)
			{
				throw new InvalidOperationException("Native food components were not cloned.");
			}
			foreach (GameObject root in _roots)
			{
				Graphic[] componentsInChildren = root.GetComponentsInChildren<Graphic>(true);
				for (int l = 0; l < componentsInChildren.Length; l++)
				{
					componentsInChildren[l].raycastTarget = false;
				}
				root.SetActive(false);
			}
			Log.LogInfo((object)"Added a berry icon and timer one native row above the food stack.");
		}

		private GameObject Clone(GameObject source, Vector3 shift)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = Object.Instantiate<GameObject>(source, source.transform.parent, false);
			_roots.Add(val);
			((Object)val).name = "BerrySlot_" + ((Object)source).name;
			val.transform.position = source.transform.position + shift;
			return val;
		}

		private static T? Counterpart<T>(Transform source, Transform sourceRoot, Transform cloneRoot) where T : Component
		{
			Stack<int> stack = new Stack<int>();
			Transform val = source;
			while ((Object)(object)val != (Object)(object)sourceRoot)
			{
				stack.Push(val.GetSiblingIndex());
				val = val.parent;
			}
			Transform val2 = cloneRoot;
			while (stack.Count > 0)
			{
				val2 = val2.GetChild(stack.Pop());
			}
			return ((Component)val2).GetComponent<T>();
		}

		private void OnDestroy()
		{
			foreach (GameObject root in _roots)
			{
				if ((Object)(object)root != (Object)null)
				{
					Object.Destroy((Object)(object)root);
				}
			}
		}
	}
	internal static class FoodRouting
	{
		internal sealed class Scope
		{
			internal readonly List<Food> Foods;

			internal readonly List<Food> Before;

			private readonly List<Food> _hidden;

			private readonly bool _bonusDestination;

			private readonly bool _readOnly;

			private bool _finished;

			internal Scope(List<Food> foods, ItemData? item, bool readOnly)
			{
				Foods = foods;
				Before = new List<Food>(foods);
				_readOnly = readOnly;
				Food berry = Reserved(Before);
				_bonusDestination = item != null && BonusDestination(Before, item);
				List<Food> collection = (_bonusDestination ? Before.Where((Food f) => f == berry && f.m_item.m_shared.m_name == item.m_shared.m_name).ToList() : Before.Where((Food f) => f != berry).ToList());
				_hidden = (_bonusDestination ? Before.Where((Food f) => f != berry).ToList() : Before.Where((Food f) => f == berry).ToList());
				Foods.Clear();
				Foods.AddRange(collection);
			}

			internal void Finish(bool success)
			{
				if (!_finished)
				{
					_finished = true;
					Food[] collection = Foods.ToArray();
					Foods.Clear();
					if (_readOnly || !success)
					{
						Foods.AddRange(Before);
					}
					else if (_bonusDestination)
					{
						Foods.AddRange(_hidden);
						Foods.AddRange(collection);
					}
					else
					{
						Foods.AddRange(collection);
						Foods.AddRange(_hidden);
					}
				}
			}
		}

		internal static bool IsBonusFood(string? prefab)
		{
			switch (prefab)
			{
			default:
				return prefab == "MushroomSmokePuff";
			case "Raspberry":
			case "Blueberries":
			case "Cloudberry":
			case "Vineberry":
			case "Lingonberry":
			case "Mushroom":
			case "MushroomYellow":
			case "MushroomBlue":
			case "MushroomJotunPuffs":
			case "MushroomMagecap":
				return true;
			}
		}

		internal static bool IsBonusFood(ItemData item)
		{
			if ((Object)(object)item.m_dropPrefab != (Object)null)
			{
				return IsBonusFood(((Object)item.m_dropPrefab).name);
			}
			return false;
		}

		internal static Food? Reserved(IReadOnlyList<Food> foods)
		{
			return ((IEnumerable<Food>)foods).LastOrDefault((Func<Food, bool>)((Food f) => IsBonusFood(f.m_name)));
		}

		internal static Food? OrdinaryMatch(IReadOnlyList<Food> foods, ItemData item)
		{
			Food reserved = Reserved(foods);
			return ((IEnumerable<Food>)foods).FirstOrDefault((Func<Food, bool>)((Food f) => f != reserved && f.m_item.m_shared.m_name == item.m_shared.m_name));
		}

		internal static bool BonusDestination(IReadOnlyList<Food> foods, ItemData item)
		{
			if (!IsBonusFood(item) || OrdinaryMatch(foods, item) != null)
			{
				return false;
			}
			Food val = Reserved(foods);
			if (val != null && !(val.m_item.m_shared.m_name == item.m_shared.m_name))
			{
				return val.CanEatAgain();
			}
			return true;
		}

		internal static bool CanUseBonus(IReadOnlyList<Food> foods)
		{
			Food val = Reserved(foods);
			if (val != null)
			{
				return val.CanEatAgain();
			}
			return true;
		}
	}
	[BepInPlugin("vegasx.valheim.berryslot", "Berry & Mushroom Slot", "0.1.2")]
	[BepInProcess("valheim.exe")]
	public sealed class Plugin : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(Player), "CanEat", new Type[]
		{
			typeof(ItemData),
			typeof(bool)
		})]
		private static class CanEatPatch
		{
			private static bool Prefix(Player __instance, ItemData item, bool showMessages, ref bool __result, out FoodRouting.Scope? __state)
			{
				__state = null;
				if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer || Eating.ContainsKey(__instance))
				{
					return true;
				}
				List<Food> foods = __instance.GetFoods();
				if (FoodRouting.BonusDestination(foods, item))
				{
					__result = FoodRouting.CanUseBonus(foods);
					if (!__result && showMessages)
					{
						((Character)__instance).Message((MessageType)2, "Your forage slot is full. Replace it after half its time has passed.", 0, (Sprite)null, false);
					}
					return false;
				}
				__state = new FoodRouting.Scope(foods, item, readOnly: true);
				return true;
			}

			private static void Finalizer(FoodRouting.Scope? __state)
			{
				__state?.Finish(success: false);
			}
		}

		[HarmonyPatch(typeof(Player), "EatFood", new Type[] { typeof(ItemData) })]
		private static class EatFoodPatch
		{
			private static bool Prefix(Player __instance, ItemData item, ref bool __result, out FoodRouting.Scope? __state)
			{
				__state = null;
				if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer)
				{
					return true;
				}
				if (Eating.ContainsKey(__instance))
				{
					__result = false;
					return false;
				}
				List<Food> foods = __instance.GetFoods();
				if (FoodRouting.BonusDestination(foods, item) && !FoodRouting.CanUseBonus(foods))
				{
					__result = false;
					return false;
				}
				__state = new FoodRouting.Scope(foods, item, readOnly: false);
				Eating.Add(__instance, __state);
				return true;
			}

			private static void Finalizer(Player __instance, bool __result, Exception? __exception, FoodRouting.Scope? __state)
			{
				if (__state != null)
				{
					__state.Finish(__result && __exception == null);
					Eating.Remove(__instance);
				}
			}
		}

		[HarmonyPatch(typeof(Player), "UpdateFood", new Type[]
		{
			typeof(float),
			typeof(bool)
		})]
		private static class UpdateFoodPatch
		{
			[HarmonyPriority(800)]
			private static void Prefix(Player __instance)
			{
				if (Eating.TryGetValue(__instance, out FoodRouting.Scope value))
				{
					value.Finish(success: true);
				}
			}
		}

		[HarmonyPatch(typeof(Hud), "UpdateFood", new Type[] { typeof(Player) })]
		private static class HudFoodPatch
		{
			[HarmonyPriority(0)]
			private static void Prefix(Hud __instance, Player player, out FoodRouting.Scope? __state)
			{
				__state = null;
				if (!((Object)(object)player != (Object)(object)Player.m_localPlayer) && !Eating.ContainsKey(player))
				{
					(((Component)__instance).GetComponent<BerryHud>() ?? ((Component)__instance).gameObject.AddComponent<BerryHud>()).Render(__instance, FoodRouting.Reserved(player.GetFoods()));
					__state = new FoodRouting.Scope(player.GetFoods(), null, readOnly: true);
				}
			}

			[HarmonyPriority(800)]
			private static void Postfix(FoodRouting.Scope? __state)
			{
				__state?.Finish(success: false);
			}

			private static void Finalizer(FoodRouting.Scope? __state)
			{
				__state?.Finish(success: false);
			}
		}

		public const string Id = "vegasx.valheim.berryslot";

		private Harmony? _harmony;

		internal static ConfigEntry<float> OffsetX = null;

		internal static ConfigEntry<float> OffsetY = null;

		internal static readonly Dictionary<Player, FoodRouting.Scope> Eating = new Dictionary<Player, FoodRouting.Scope>();

		private void Awake()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			OffsetX = ((BaseUnityPlugin)this).Config.Bind<float>("Interface", "HorizontalOffset", 0f, "Move only the berry slot horizontally, in scaled HUD units. Restart to apply.");
			OffsetY = ((BaseUnityPlugin)this).Config.Bind<float>("Interface", "VerticalOffset", 0f, "Move only the berry slot vertically. Positive moves up. Restart to apply.");
			_harmony = new Harmony("vegasx.valheim.berryslot");
			try
			{
				_harmony.PatchAll(typeof(Plugin).Assembly);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Berry Slot 0.1.2 loaded: all-biome raw forage, priority with ordinary-slot fallback.");
			}
			catch (Exception ex)
			{
				_harmony.UnpatchSelf();
				((BaseUnityPlugin)this).Logger.LogError((object)("Berry Slot could not patch this game version; disabled. " + ex));
			}
		}

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