Decompiled source of Instant Fertilizer v0.3.0

plugins/InstantFertilizer.dll

Decompiled 4 months ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using InstantFertilizer.Model;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")]
[assembly: AssemblyCompany("InstantFertilizer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.3.0.0")]
[assembly: AssemblyInformationalVersion("0.3.0+1b375adfddd34ea836b8f1edad4108682ce91061")]
[assembly: AssemblyProduct("InstantFertilizer")]
[assembly: AssemblyTitle("InstantFertilizer")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.3.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 InstantFertilizer
{
	[BepInPlugin("nbusseneau.InstantFertilizer", "InstantFertilizer", "0.3.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		internal const string ModGUID = "nbusseneau.InstantFertilizer";

		private const string ModName = "InstantFertilizer";

		private const string ModVersion = "0.3.0";

		private static readonly List<Fertilizer> s_defaultFertilizers = new List<Fertilizer>(2)
		{
			new Fertilizer("$item_ancientseed", 3, "defeated_eikthyr"),
			new Fertilizer("$item_ymirremains", 1, "defeated_gdking")
		};

		private static ConfigEntry<string> s_fertilizers;

		private static ConfigEntry<int> s_fertilizePercentage;

		internal static ManualLogSource Logger { get; private set; }

		public static List<Fertilizer> Fertilizers { get; private set; }

		public static float FertilizePercentage => (float)s_fertilizePercentage.Value / 100f;

		private static List<Fertilizer> ParseFertilizers(string serializedFertilizers)
		{
			return (from f in serializedFertilizers.Split(new char[1] { ',' }).Select(Fertilizer.FromString)
				where f != null
				select f).ToList();
		}

		public void Awake()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Expected O, but got Unknown
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Expected O, but got Unknown
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			Logger = ((BaseUnityPlugin)this).Logger;
			ConfigurationManagerAttributes val = new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			};
			string text = "Comma-separated list of fertilizers that may be used.\nFertilizer format: " + Fertilizer.SerializedFormat + "\nSee https://valheim.fandom.com/wiki/Global_Keys for quick reference of available global keys, or use `none` if you do not want to gate fertilizing behind a global key.\nNote that the mod is not able to determine in advance if an item or global key actually exists. If a fertilizer appears to be ignored, double check item names and global keys.";
			s_fertilizers = ((BaseUnityPlugin)this).Config.Bind<string>("Behaviour", "Fertilizer list", GeneralExtensions.Join<Fertilizer>((IEnumerable<Fertilizer>)s_defaultFertilizers, (Func<Fertilizer, string>)null, ", "), new ConfigDescription(text, (AcceptableValueBase)null, new object[1] { val }));
			Fertilizers = ParseFertilizers(s_fertilizers.Value);
			s_fertilizers.SettingChanged += delegate
			{
				Fertilizers = ParseFertilizers(s_fertilizers.Value);
			};
			string text2 = "Reduce remaining time by this amount when fertilizing (in percentage of total growing / respawning time).\nDefault value of 100%: grow / respawn instantaneously.\nA single plant / pickable can be fertilized multiple times, but not more than once with the same fertilizer.";
			AcceptableValueRange<int> val2 = new AcceptableValueRange<int>(1, 100);
			s_fertilizePercentage = ((BaseUnityPlugin)this).Config.Bind<int>("Behaviour", "Fertilize percentage", 100, new ConfigDescription(text2, (AcceptableValueBase)(object)val2, new object[1] { val }));
			SetUpConfigWatcher();
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			new Harmony("nbusseneau.InstantFertilizer").PatchAll(executingAssembly);
		}

		public void OnDestroy()
		{
			((BaseUnityPlugin)this).Config.Save();
		}

		private void SetUpConfigWatcher()
		{
			FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(Paths.ConfigPath, Path.GetFileName(((BaseUnityPlugin)this).Config.ConfigFilePath));
			fileSystemWatcher.Changed += ReadConfigValues;
			fileSystemWatcher.Created += ReadConfigValues;
			fileSystemWatcher.Renamed += ReadConfigValues;
			fileSystemWatcher.IncludeSubdirectories = true;
			fileSystemWatcher.SynchronizingObject = ThreadingHelper.SynchronizingObject;
			fileSystemWatcher.EnableRaisingEvents = true;
		}

		private void ReadConfigValues(object sender, FileSystemEventArgs e)
		{
			if (!File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath))
			{
				return;
			}
			try
			{
				Logger.LogDebug((object)"Attempting to reload configuration...");
				((BaseUnityPlugin)this).Config.Reload();
			}
			catch
			{
				Logger.LogError((object)("There was an issue loading " + ((BaseUnityPlugin)this).Config.ConfigFilePath));
			}
		}
	}
}
namespace InstantFertilizer.Patches
{
	[HarmonyPatch(typeof(Pickable))]
	public class PickablePatches
	{
		[HarmonyPostfix]
		[HarmonyPatch("GetHoverText")]
		private static void PickableGetFertilizeHoverText(Pickable __instance, ref string __result)
		{
			bool num = FertilizerManager.CanFertilize(__instance);
			Vine component = ((Component)__instance).GetComponent<Vine>();
			bool flag = component != null && FertilizerManager.CanFertilize(component);
			if (num || flag)
			{
				__result += FertilizerManager.GetFertilizeHoverText(__instance.m_nview);
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch("SetPicked")]
		private static void PickableResetFertilizedWith(Pickable __instance, bool picked)
		{
			if (picked && Object.op_Implicit((Object)(object)__instance.m_nview) && __instance.m_nview.IsValid() && __instance.m_nview.GetZDO().GetBool(ZDOVars.s_picked, false))
			{
				Plugin.Fertilizers.ForEach(delegate(Fertilizer fertilizer)
				{
					FertilizerManager.SetWasFertilizedWith(fertilizer, __instance.m_nview, value: false);
				});
			}
		}
	}
	[HarmonyPatch(typeof(Plant))]
	public class PlantPatches
	{
		[HarmonyPostfix]
		[HarmonyPatch("GetHoverText")]
		private static void PlantGetFertilizeHoverText(Plant __instance, ref string __result)
		{
			if (FertilizerManager.CanFertilize(__instance))
			{
				__result += FertilizerManager.GetFertilizeHoverText(__instance.m_nview);
			}
		}
	}
	[HarmonyPatch(typeof(Player))]
	public class PlayerPatches
	{
		[HarmonyPrefix]
		[HarmonyPatch("Interact")]
		private static void Fertilize(Player __instance, GameObject go, bool hold, ref bool __runOriginal)
		{
			if (((Character)__instance).InAttack() || ((Character)__instance).InDodge() || hold)
			{
				return;
			}
			bool flag = false;
			Plant componentInParent = go.GetComponentInParent<Plant>();
			if (componentInParent != null && FertilizerManager.CanFertilize(componentInParent))
			{
				flag = FertilizerManager.TryFertilize(__instance, componentInParent);
			}
			else
			{
				Vine componentInParent2 = go.GetComponentInParent<Vine>();
				if (componentInParent2 != null && FertilizerManager.CanFertilize(componentInParent2))
				{
					flag = FertilizerManager.TryFertilize(__instance, componentInParent2);
				}
				else
				{
					Pickable componentInParent3 = go.GetComponentInParent<Pickable>();
					if (componentInParent3 != null && FertilizerManager.CanFertilize(componentInParent3))
					{
						flag = FertilizerManager.TryFertilize(__instance, componentInParent3);
					}
				}
			}
			if (flag)
			{
				__runOriginal = false;
			}
		}
	}
	[HarmonyPatch(typeof(Vine))]
	public class VinePatches
	{
		[HarmonyTranspiler]
		[HarmonyPatch("Grow")]
		private static IEnumerable<CodeInstruction> SetWasVineFertilizedOnChildVines(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Expected O, but got Unknown
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Expected O, but got Unknown
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Expected O, but got Unknown
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Expected O, but got Unknown
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Expected O, but got Unknown
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Expected O, but got Unknown
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Expected O, but got Unknown
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Expected O, but got Unknown
			return new CodeMatcher(instructions, (ILGenerator)null).MatchEndForward((CodeMatch[])(object)new CodeMatch[8]
			{
				new CodeMatch((OpCode?)OpCodes.Callvirt, (object)AccessTools.Method(typeof(ZNetView), "GetZDO", (Type[])null, (Type[])null), (string)null),
				new CodeMatch((OpCode?)OpCodes.Ldsfld, (object)AccessTools.Field(typeof(ZDOVars), "s_plantTime"), (string)null),
				new CodeMatch((OpCode?)OpCodes.Call, (object)null, (string)null),
				new CodeMatch((OpCode?)OpCodes.Callvirt, (object)AccessTools.Method(typeof(ZNet), "GetTime", (Type[])null, (Type[])null), (string)null),
				new CodeMatch((OpCode?)OpCodes.Stloc_2, (object)null, (string)null),
				new CodeMatch((OpCode?)OpCodes.Ldloca_S, (object)null, (string)null),
				new CodeMatch((OpCode?)OpCodes.Call, (object)null, (string)null),
				new CodeMatch((OpCode?)OpCodes.Callvirt, (object)AccessTools.Method(typeof(ZDO), "Set", new Type[2]
				{
					typeof(int),
					typeof(long)
				}, (Type[])null), (string)null)
			}).ThrowIfInvalid("Could not inject WasVineGrown status to child vine in Vine.Grow(...)").Advance(1)
				.InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1]
				{
					new CodeInstruction(OpCodes.Ldarg_0, (object)null)
				})
				.InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1]
				{
					new CodeInstruction(OpCodes.Ldloc_0, (object)null)
				})
				.InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { Transpilers.EmitDelegate<Action<Vine, Vine>>((Action<Vine, Vine>)delegate(Vine parentVine, Vine childVine)
				{
					if (FertilizerManager.WasVineFertilized(parentVine))
					{
						FertilizerManager.SetWasVineFertilized(childVine, value: true);
					}
				}) })
				.InstructionEnumeration();
		}
	}
	[HarmonyPatch(typeof(ObjectDB))]
	public class ZNetScenePatches
	{
		[HarmonyPostfix]
		[HarmonyPatch("Awake")]
		[HarmonyPatch("CopyOtherDB")]
		private static void ClearCachedGlobalKeys()
		{
			FertilizerManager.ClearCachedGlobalKeys();
		}
	}
}
namespace InstantFertilizer.Model
{
	public class Fertilizer
	{
		public const char Delimiter = ':';

		public static readonly string SerializedFormat = $"itemName{':'}requiredAmount{':'}requiredGlobalKey";

		public string ItemName { get; }

		public int RequiredAmount { get; }

		public string RequiredGlobalKey { get; }

		public Fertilizer(string itemName, int requiredAmount, string requiredGlobalKey)
		{
			ItemName = itemName;
			RequiredAmount = requiredAmount;
			RequiredGlobalKey = requiredGlobalKey;
			base..ctor();
		}

		public override string ToString()
		{
			return $"{ItemName}{':'}{RequiredAmount}{':'}{RequiredGlobalKey}";
		}

		public static Fertilizer FromString(string serializedFertilizer)
		{
			string[] array = serializedFertilizer.Trim().Split(new char[1] { ':' });
			if (array.Length != 3)
			{
				Plugin.Logger.LogError((object)("Could not deserialize the following fertilizer entry: " + serializedFertilizer + "\r\nInvalid format: must be `" + SerializedFormat + "`"));
				return null;
			}
			string text = array[0];
			string s = array[1];
			string requiredGlobalKey = array[2];
			string itemName = text;
			if (!int.TryParse(s, out var result))
			{
				Plugin.Logger.LogError((object)("Could not deserialize the following fertilizer entry: " + serializedFertilizer + "\r\nInvalid amount: must be a valid integer"));
				return null;
			}
			return new Fertilizer(itemName, result, requiredGlobalKey);
		}
	}
	public static class FertilizerManager
	{
		public const string GlobalKeyIgnore = "none";

		private static readonly Dictionary<string, bool> s_cachedGlobalKeys = new Dictionary<string, bool>();

		private static readonly string WasFertilizedWithZDOKey = "nbusseneau.InstantFertilizer.WasFertilizedWith";

		public static void ClearCachedGlobalKeys()
		{
			s_cachedGlobalKeys.Clear();
		}

		private static bool HasGlobalKey(string globalKey)
		{
			if (globalKey == "none")
			{
				return true;
			}
			if (s_cachedGlobalKeys.TryGetValue(globalKey, out var value))
			{
				return value;
			}
			value = ZoneSystem.instance.GetGlobalKey(globalKey);
			if (value)
			{
				s_cachedGlobalKeys[globalKey] = true;
			}
			return value;
		}

		private static bool WasFertilizedWith(Fertilizer fertilizer, ZNetView nview)
		{
			return nview.GetZDO().GetBool(WasFertilizedWithZDOKey + "." + fertilizer.ItemName, false);
		}

		public static void SetWasFertilizedWith(Fertilizer fertilizer, ZNetView nview, bool value)
		{
			nview.GetZDO().Set(WasFertilizedWithZDOKey + "." + fertilizer.ItemName, value);
		}

		public static bool WasVineFertilized(Vine vine)
		{
			return vine.m_nview.GetZDO().GetBool(WasFertilizedWithZDOKey, false);
		}

		public static void SetWasVineFertilized(Vine vine, bool value)
		{
			vine.m_nview.GetZDO().Set(WasFertilizedWithZDOKey, value);
		}

		public static string GetFertilizeHoverText(ZNetView nview)
		{
			string text = string.Empty;
			IEnumerable<string> wasFertilizedWith = from fertilizer in Plugin.Fertilizers
				where WasFertilizedWith(fertilizer, nview)
				select fertilizer.ItemName;
			if (wasFertilizedWith.Any())
			{
				text = text + "\n$InstantFertilizer_FertilizedWith " + string.Join(" / ", wasFertilizedWith);
			}
			IEnumerable<string> enumerable = from fertilizer in Plugin.Fertilizers
				where HasGlobalKey(fertilizer.RequiredGlobalKey) && !wasFertilizedWith.Contains(fertilizer.ItemName)
				select $"{fertilizer.RequiredAmount} {fertilizer.ItemName}";
			if (enumerable.Any())
			{
				text = text + "\n[<color=yellow><b>$KEY_Use</b></color>] $InstantFertilizer_Fertilize (" + string.Join(" / ", enumerable) + ")";
			}
			return Localization.instance.Localize(text);
		}

		public static bool CanFertilize(Pickable pickable)
		{
			if (pickable.CanBePicked() || !Object.op_Implicit((Object)(object)pickable.m_nview) || !pickable.m_nview.IsValid())
			{
				return false;
			}
			Vine component = ((Component)pickable).GetComponent<Vine>();
			if (component != null && !component.CanSpawnPickable(pickable))
			{
				return false;
			}
			return (ZNet.instance.GetTime() - new DateTime(pickable.m_nview.GetZDO().GetLong(ZDOVars.s_pickedTime, 0L))).TotalMinutes <= (double)pickable.m_respawnTimeMinutes;
		}

		public static bool CanFertilize(Vine vine)
		{
			if (!vine.IsDoneGrowing && Object.op_Implicit((Object)(object)vine.m_nview) && vine.m_nview.IsValid() && !WasVineFertilized(vine))
			{
				return !vine.m_pickable.CanBePicked();
			}
			return false;
		}

		public static bool CanFertilize(Plant plant)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			if ((int)plant.m_status == 0 && Object.op_Implicit((Object)(object)plant.m_nview) && plant.m_nview.IsValid())
			{
				return plant.TimeSincePlanted() <= (double)plant.GetGrowTime();
			}
			return false;
		}

		public static bool TryFertilize(Player player, Pickable pickable)
		{
			return TryFertilizeInternal(player, pickable.m_nview, delegate
			{
				pickable.m_nview.ClaimOwnership();
				if (pickable.m_pickedTime == 0L)
				{
					pickable.UpdateRespawn();
				}
				DateTime dateTime = new DateTime(pickable.m_nview.GetZDO().GetLong(ZDOVars.s_pickedTime, 0L));
				dateTime -= TimeSpan.FromMinutes(pickable.m_respawnTimeMinutes * Plugin.FertilizePercentage);
				pickable.m_nview.GetZDO().Set(ZDOVars.s_pickedTime, dateTime.Ticks);
				pickable.UpdateRespawn();
			});
		}

		public static bool TryFertilize(Player player, Vine vine)
		{
			return TryFertilizeInternal(player, vine.m_nview, delegate
			{
				vine.m_nview.ClaimOwnership();
				SetWasVineFertilized(vine, value: true);
				vine.m_initialGrowItterations = 25;
			}, setWasFertilizedWith: false);
		}

		public static bool TryFertilize(Player player, Plant plant)
		{
			return TryFertilizeInternal(player, plant.m_nview, delegate
			{
				plant.m_nview.ClaimOwnership();
				DateTime dateTime = new DateTime(plant.m_nview.GetZDO().GetLong(ZDOVars.s_plantTime, 0L));
				dateTime -= TimeSpan.FromSeconds(plant.GetGrowTime() * Plugin.FertilizePercentage);
				plant.m_nview.GetZDO().Set(ZDOVars.s_plantTime, dateTime.Ticks);
				Plant obj = plant;
				obj.m_updateTime -= 100f;
				Plant obj2 = plant;
				obj2.m_spawnTime -= 100f;
				((SlowUpdate)plant).SUpdate();
			});
		}

		private static bool TryFertilizeInternal(Player player, ZNetView nview, Action onFertilize, bool setWasFertilizedWith = true)
		{
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			player.m_lastHoverInteractTime = Time.time;
			IEnumerable<Fertilizer> enumerable = Plugin.Fertilizers.Where((Fertilizer fertilizer) => HasGlobalKey(fertilizer.RequiredGlobalKey) && !WasFertilizedWith(fertilizer, nview));
			if (!enumerable.Any())
			{
				return false;
			}
			bool flag = false;
			foreach (Fertilizer item2 in enumerable)
			{
				ItemData item = ((Humanoid)player).m_inventory.GetItem(item2.ItemName, -1, false);
				flag = item != null && ((Humanoid)player).m_inventory.CountItems(item2.ItemName, -1, true) >= item2.RequiredAmount && ((Humanoid)player).m_inventory.RemoveItem(item, item2.RequiredAmount);
				if (flag && setWasFertilizedWith)
				{
					SetWasFertilizedWith(item2, nview, value: true);
					break;
				}
			}
			if (!flag)
			{
				((Character)player).Message((MessageType)2, "$InstantFertilizer_FertilizerRequired", 0, (Sprite)null);
				return false;
			}
			onFertilize();
			((Humanoid)player).DoInteractAnimation(((Component)nview).transform.position);
			return true;
		}
	}
}