Decompiled source of TroughFoodCompatibility v0.1.2

plugins/TroughFoodCompatibility/TroughFoodCompatibility.dll

Decompiled a day 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.Cryptography;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using ConditionalConfigSync;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[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("TroughFoodCompatibility")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.2.0")]
[assembly: AssemblyInformationalVersion("0.1.2")]
[assembly: AssemblyProduct("TroughFoodCompatibility")]
[assembly: AssemblyTitle("TroughFoodCompatibility")]
[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.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace TroughFoodCompatibility
{
	internal static class FoodRules
	{
		internal static readonly string[] OriginalFoods = new string[5] { "Carrot", "Turnip", "Onion", "Raspberry", "Blueberries" };

		internal static HashSet<string> Parse(string text)
		{
			return new HashSet<string>(from x in (text ?? "").Split(new char[1] { ',' })
				select x.Trim() into x
				where x.Length > 0
				select x, StringComparer.Ordinal);
		}

		internal static string Prefab(string name)
		{
			string text;
			if (name == null || !name.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				text = name;
				if (text == null)
				{
					return "";
				}
			}
			else
			{
				text = name.Substring(0, name.Length - 7);
			}
			return text;
		}

		internal static IEnumerable<string> MeatEquivalents(IEnumerable<string> diet)
		{
			HashSet<string> foods = new HashSet<string>(diet, StringComparer.Ordinal);
			if (foods.Contains("RawMeat"))
			{
				yield return "rk_pork";
			}
			if (foods.Contains("HareMeat"))
			{
				yield return "BOH_RawHare_Meat";
			}
		}

		internal static IEnumerable<string> AdditionalDiets(string creature, IEnumerable<string> diet, bool wolfHare, bool seafood, bool oceanFish, bool henSeeds)
		{
			HashSet<string> foods = new HashSet<string>(diet, StringComparer.Ordinal);
			if (wolfHare && creature == "Wolf")
			{
				yield return "HareMeat";
				yield return "BOH_RawHare_Meat";
			}
			if (seafood && foods.Contains("FishRaw"))
			{
				yield return "Meat_Cod_RtD";
				yield return "Meat_Tuna_RtD";
				yield return "Meat_Marlin_RtD";
			}
			if (oceanFish && (creature == "Monster_Orca_RtD" || creature == "Mirmaid_RtD"))
			{
				yield return "FishRaw";
			}
			if (henSeeds && creature == "Hen")
			{
				yield return "Seed_Rice_RtD";
				yield return "Seed_Wheat_RtD";
				yield return "Seed_Corn_RtD";
			}
		}

		internal static string Signature(IEnumerable<string> diet)
		{
			return string.Join(",", diet.Distinct().OrderBy<string, string>((string x) => x, StringComparer.Ordinal));
		}

		internal static bool RejectMixed<T>(IEnumerable<T> items, Func<T, bool> invalidFood, bool enabled)
		{
			if (!enabled)
			{
				return items.Any(invalidFood);
			}
			return false;
		}
	}
	[BepInPlugin("vercadi.TroughFoodCompatibility", "Trough Food Compatibility", "0.1.2")]
	[BepInDependency("MagicMike.CoreWoodExtras", "2.2.7")]
	[BepInDependency("_shudnal.ConditionalConfigSync", "1.0.9")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class DietState
		{
			internal int Revision = -1;

			internal List<ItemDrop> List;

			internal string Signature;

			internal readonly List<ItemDrop> Added = new List<ItemDrop>();
		}

		public const string Id = "vercadi.TroughFoodCompatibility";

		internal const string SupportedHash227 = "EBEE48CF9F4675FCEE70FDBE6CC3768D4E9CFC564613BDE4CFFC2EF3A47EB62F";

		internal const string SupportedHash228 = "D8017BEA62F3201C693B5D6B5BFB5E219DA1E0E637CC0399BBC362A3C94540D2";

		private static Plugin instance;

		private Harmony harmony;

		private ConfigSync sync;

		private ConfigEntry<bool> enabledFeature;

		private ConfigEntry<bool> allowCollect;

		private ConfigEntry<bool> meatEquivalents;

		private ConfigEntry<bool> wolfHare;

		private ConfigEntry<bool> seafood;

		private ConfigEntry<bool> oceanFish;

		private ConfigEntry<bool> henSeeds;

		private ConfigEntry<string> herbivores;

		private ConfigEntry<string> extraFoods;

		private HashSet<string> herbivoreNames;

		private HashSet<string> extraFoodNames;

		private HashSet<string> troughFoods;

		private readonly HashSet<string> additions = new HashSet<string>(StringComparer.Ordinal);

		private readonly HashSet<string> observedFoods = new HashSet<string>(StringComparer.Ordinal);

		private readonly ConditionalWeakTable<MonsterAI, DietState> dietStates = new ConditionalWeakTable<MonsterAI, DietState>();

		private int revision;

		private bool ready;

		private static bool Active
		{
			get
			{
				if (Object.op_Implicit((Object)(object)instance) && instance.ready)
				{
					return instance.enabledFeature.Value;
				}
				return false;
			}
		}

		private void Awake()
		{
			//IL_013d: 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_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Expected O, but got Unknown
			//IL_041f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0429: Expected O, but got Unknown
			//IL_0441: Unknown result type (might be due to invalid IL or missing references)
			//IL_0457: Unknown result type (might be due to invalid IL or missing references)
			//IL_0463: Expected O, but got Unknown
			//IL_0463: Expected O, but got Unknown
			//IL_047c: Unknown result type (might be due to invalid IL or missing references)
			//IL_048a: Expected O, but got Unknown
			instance = this;
			enabledFeature = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Accept registered creature foods in CoreWoodExtras troughs and allow mixed-food troughs.");
			allowCollect = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Allow trough auto collect", false, "Allow CoreWoodExtras automatic ground pickup. Its own option must also be enabled. Expanded diets can include valuable meals and materials; disabled by default.");
			meatEquivalents = ((BaseUnityPlugin)this).Config.Bind<bool>("Optional diets", "Raw meat equivalents", true, "Animals that eat vanilla boar meat also accept BoneAppetit Raw Pork (rk_pork). Animals that eat vanilla hare meat also accept OdinsHares raw hare meat (BOH_RawHare_Meat). Does not give meat to herbivores. Missing mods are ignored.");
			herbivores = ((BaseUnityPlugin)this).Config.Bind<string>("Optional diets", "Creature prefabs", "Deer,Boar,Sheep_TW", "Exact creatures whose diets receive Extra food prefabs. Empty extra-food list makes no diet changes.");
			extraFoods = ((BaseUnityPlugin)this).Config.Bind<string>("Optional diets", "Extra food prefabs", "", "Optional comma-separated installed item prefab names. Adds to these creatures' diets without replacing existing food. Empty by default. Missing items are ignored.");
			wolfHare = ((BaseUnityPlugin)this).Config.Bind<bool>("Optional diets", "Wolves eat hare meat", false, "Let ordinary wolves eat vanilla and OdinsHares raw hare meat.");
			seafood = ((BaseUnityPlugin)this).Config.Bind<bool>("Optional diets", "Raw fish equivalents", false, "Creatures already eating FishRaw also accept installed RtD cod, tuna and marlin meats.");
			oceanFish = ((BaseUnityPlugin)this).Config.Bind<bool>("Optional diets", "Ocean companion fish fallback", false, "RtD orcas and mermaids also accept vanilla FishRaw. Dolphins retain their shrimp diet.");
			henSeeds = ((BaseUnityPlugin)this).Config.Bind<bool>("Optional diets", "Hens eat RtD seeds", false, "Adult hens accept installed RtD rice, wheat and corn seeds. Does not change baby chickens.");
			sync = new ConfigSync("vercadi.TroughFoodCompatibility")
			{
				DisplayName = "Trough Food Compatibility",
				CurrentVersion = "0.1.2",
				MinimumRequiredVersion = "0.1.2",
				ModRequired = true
			};
			((ConditionalConfigSync)sync).AddLockingConfigEntry<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("General", "Lock server settings", true, "Only server administrators may change shared settings."));
			((ConditionalConfigSync)sync).AddConfigEntry<bool>(enabledFeature, (ConfigSyncMode)0);
			((ConditionalConfigSync)sync).AddConfigEntry<bool>(allowCollect, (ConfigSyncMode)0);
			((ConditionalConfigSync)sync).AddConfigEntry<string>(herbivores, (ConfigSyncMode)0);
			((ConditionalConfigSync)sync).AddConfigEntry<string>(extraFoods, (ConfigSyncMode)0);
			((ConditionalConfigSync)sync).AddConfigEntry<bool>(meatEquivalents, (ConfigSyncMode)0);
			ConfigEntry<bool>[] array = new ConfigEntry<bool>[4] { wolfHare, seafood, oceanFish, henSeeds };
			foreach (ConfigEntry<bool> val in array)
			{
				((ConditionalConfigSync)sync).AddConfigEntry<bool>(val, (ConfigSyncMode)0);
				val.SettingChanged += Changed;
			}
			enabledFeature.SettingChanged += Changed;
			herbivores.SettingChanged += Changed;
			extraFoods.SettingChanged += Changed;
			meatEquivalents.SettingChanged += Changed;
			ReadSettings();
			try
			{
				Assembly assembly = ((object)Chainloader.PluginInfos["MagicMike.CoreWoodExtras"].Instance).GetType().Assembly;
				using (FileStream inputStream = File.OpenRead(assembly.Location))
				{
					using SHA256 sHA = SHA256.Create();
					string text = BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "");
					if (text != "EBEE48CF9F4675FCEE70FDBE6CC3768D4E9CFC564613BDE4CFFC2EF3A47EB62F" && text != "D8017BEA62F3201C693B5D6B5BFB5E219DA1E0E637CC0399BBC362A3C94540D2")
					{
						throw new NotSupportedException("CoreWoodExtras binary differs from the reviewed 2.2.7 / 2.2.8 releases.");
					}
				}
				Type type = assembly.GetType("CoreWoodExtras.AddDecor", throwOnError: true);
				troughFoods = (HashSet<string>)AccessTools.Field(type, "troughItems").GetValue(null);
				MethodInfo methodInfo = AccessTools.Method(assembly.GetType("CoreWoodExtras.Containers.UtilityMethods", throwOnError: true), "TryFeedAnimal", new Type[3]
				{
					typeof(MonsterAI),
					typeof(Tameable),
					typeof(Character)
				}, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.Method(assembly.GetType("CoreWoodExtras.Containers.TroughAutoCollect", throwOnError: true), "CollectNearbyItems", (Type[])null, (Type[])null);
				if (methodInfo == null || methodInfo2 == null || troughFoods == null || !FoodRules.OriginalFoods.All(troughFoods.Contains))
				{
					throw new NotSupportedException("Reviewed trough contracts were not found.");
				}
				harmony = new Harmony("vercadi.TroughFoodCompatibility");
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(Plugin), "BeforeFeed", (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "MixedFoodTranspiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(Plugin), "AllowCollect", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(ZNetScene), "Awake", (Type[])null, (Type[])null), (HarmonyMethod)null, Last("SceneReady"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(MonsterAI), "Awake", (Type[])null, (Type[])null), (HarmonyMethod)null, Last("AnimalReady"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				ready = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("ACTIVE: reviewed CoreWoodExtras 2.2.7 / 2.2.8 trough food discovery and mixed-food filter registered. Auto collect=" + allowCollect.Value + "."));
			}
			catch (Exception ex)
			{
				ready = false;
				Harmony obj = harmony;
				if (obj != null)
				{
					obj.UnpatchSelf();
				}
				((BaseUnityPlugin)this).Logger.LogError((object)("INACTIVE: trough compatibility was not applied. " + ex));
			}
		}

		private static HarmonyMethod Last(string name)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Expected O, but got Unknown
			return new HarmonyMethod(typeof(Plugin), name, (Type[])null)
			{
				priority = 0
			};
		}

		private void ReadSettings()
		{
			herbivoreNames = FoodRules.Parse(herbivores.Value);
			extraFoodNames = FoodRules.Parse(extraFoods.Value);
			revision++;
		}

		private void Changed(object sender, EventArgs args)
		{
			ReadSettings();
			if (ready && Object.op_Implicit((Object)(object)ZNetScene.instance))
			{
				RefreshScene(ZNetScene.instance);
			}
			if (!enabledFeature.Value)
			{
				RemoveWhitelistAdditions();
			}
		}

		private void RemoveWhitelistAdditions()
		{
			if (troughFoods != null)
			{
				foreach (string addition in additions)
				{
					troughFoods.Remove(addition);
				}
			}
			additions.Clear();
		}

		private static void SceneReady(ZNetScene __instance)
		{
			if (Object.op_Implicit((Object)(object)instance) && instance.ready)
			{
				instance.RemoveWhitelistAdditions();
				instance.observedFoods.Clear();
				instance.revision++;
				instance.RefreshScene(__instance);
				((BaseUnityPlugin)instance).Logger.LogInfo((object)("Trough food discovery: " + instance.troughFoods.Count + " allowed item prefabs. Optional herbivore additions=" + instance.extraFoodNames.Count + "."));
			}
		}

		private void RefreshScene(ZNetScene scene)
		{
			foreach (GameObject prefab in scene.m_prefabs)
			{
				if (Object.op_Implicit((Object)(object)prefab) && Object.op_Implicit((Object)(object)prefab.GetComponent<Tameable>()))
				{
					MonsterAI component = prefab.GetComponent<MonsterAI>();
					if (Object.op_Implicit((Object)(object)component))
					{
						DiscoverFoods(component);
					}
				}
			}
			if (enabledFeature.Value && Object.op_Implicit((Object)(object)ObjectDB.instance))
			{
				foreach (string extraFoodName in extraFoodNames)
				{
					GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(extraFoodName);
					if (Object.op_Implicit((Object)(object)itemPrefab) && Object.op_Implicit((Object)(object)itemPrefab.GetComponent<ItemDrop>()))
					{
						observedFoods.Add(extraFoodName);
					}
				}
			}
			if (!enabledFeature.Value)
			{
				return;
			}
			foreach (string observedFood in observedFoods)
			{
				AddAllowed(observedFood);
			}
		}

		private static void AnimalReady(MonsterAI __instance)
		{
			if (Object.op_Implicit((Object)(object)instance) && instance.ready)
			{
				instance.Observe(__instance);
			}
		}

		private static void BeforeFeed(MonsterAI __0)
		{
			if (Object.op_Implicit((Object)(object)instance) && instance.ready)
			{
				instance.Observe(__0);
			}
		}

		private void Observe(MonsterAI ai)
		{
			if (!Object.op_Implicit((Object)(object)ai) || !Object.op_Implicit((Object)(object)((Component)ai).GetComponent<Tameable>()) || ai.m_consumeItems == null)
			{
				return;
			}
			DietState state = dietStates.GetValue(ai, (MonsterAI key) => new DietState());
			List<ItemDrop> list = ai.m_consumeItems.Where((ItemDrop x) => Object.op_Implicit((Object)(object)x) && (state.List != ai.m_consumeItems || !state.Added.Contains(x))).ToList();
			string[] diet = list.Select((ItemDrop x) => FoodRules.Prefab(((Object)((Component)x).gameObject).name)).ToArray();
			string text = FoodRules.Signature(diet);
			if (state.Revision != revision || state.List != ai.m_consumeItems || state.Signature != text)
			{
				state.Added.Clear();
				state.Revision = revision;
				state.Signature = text;
				ai.m_consumeItems = list;
				if (enabledFeature.Value && Object.op_Implicit((Object)(object)ObjectDB.instance))
				{
					HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
					if (herbivoreNames.Contains(FoodRules.Prefab(((Object)((Component)ai).gameObject).name)))
					{
						hashSet.UnionWith(extraFoodNames);
					}
					if (meatEquivalents.Value)
					{
						hashSet.UnionWith(FoodRules.MeatEquivalents(diet));
					}
					hashSet.UnionWith(FoodRules.AdditionalDiets(FoodRules.Prefab(((Object)((Component)ai).gameObject).name), diet, wolfHare.Value, seafood.Value, oceanFish.Value, henSeeds.Value));
					foreach (string name in hashSet)
					{
						GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(name);
						ItemDrop val = (Object.op_Implicit((Object)(object)itemPrefab) ? itemPrefab.GetComponent<ItemDrop>() : null);
						if (Object.op_Implicit((Object)(object)val) && !ai.m_consumeItems.Any((ItemDrop x) => Object.op_Implicit((Object)(object)x) && FoodRules.Prefab(((Object)((Component)x).gameObject).name) == name))
						{
							ai.m_consumeItems.Add(val);
							state.Added.Add(val);
						}
					}
				}
				state.List = ai.m_consumeItems;
			}
			DiscoverFoods(ai);
		}

		private void DiscoverFoods(MonsterAI ai)
		{
			if (ai.m_consumeItems == null)
			{
				return;
			}
			foreach (ItemDrop consumeItem in ai.m_consumeItems)
			{
				if (!Object.op_Implicit((Object)(object)consumeItem) || consumeItem.m_itemData?.m_shared == null)
				{
					continue;
				}
				string text = FoodRules.Prefab(((Object)((Component)consumeItem).gameObject).name);
				if (text.Length != 0)
				{
					observedFoods.Add(text);
					if (enabledFeature.Value)
					{
						AddAllowed(text);
					}
				}
			}
			if (enabledFeature.Value && meatEquivalents.Value && Object.op_Implicit((Object)(object)ObjectDB.instance))
			{
				foreach (string item in FoodRules.MeatEquivalents(from x in ai.m_consumeItems
					where Object.op_Implicit((Object)(object)x)
					select FoodRules.Prefab(((Object)((Component)x).gameObject).name)))
				{
					GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(item);
					if (Object.op_Implicit((Object)(object)itemPrefab) && Object.op_Implicit((Object)(object)itemPrefab.GetComponent<ItemDrop>()))
					{
						AddAllowed(item);
					}
				}
			}
			if (!enabledFeature.Value || !Object.op_Implicit((Object)(object)ObjectDB.instance))
			{
				return;
			}
			foreach (string item2 in FoodRules.AdditionalDiets(FoodRules.Prefab(((Object)((Component)ai).gameObject).name), from x in ai.m_consumeItems
				where Object.op_Implicit((Object)(object)x)
				select FoodRules.Prefab(((Object)((Component)x).gameObject).name), wolfHare.Value, seafood.Value, oceanFish.Value, henSeeds.Value))
			{
				GameObject itemPrefab2 = ObjectDB.instance.GetItemPrefab(item2);
				if (Object.op_Implicit((Object)(object)itemPrefab2) && Object.op_Implicit((Object)(object)itemPrefab2.GetComponent<ItemDrop>()))
				{
					AddAllowed(item2);
				}
			}
		}

		private void AddAllowed(string name)
		{
			if (troughFoods.Add(name))
			{
				additions.Add(name);
			}
		}

		private static bool AllowCollect()
		{
			if (Active)
			{
				return instance.allowCollect.Value;
			}
			return true;
		}

		internal static IEnumerable<CodeInstruction> MixedFoodTranspiler(IEnumerable<CodeInstruction> source)
		{
			List<CodeInstruction> list = source.ToList();
			CodeInstruction[] array = list.Where((CodeInstruction i) => i.operand is MethodInfo methodInfo && methodInfo.DeclaringType == typeof(Enumerable) && methodInfo.Name == "Any" && methodInfo.GetParameters().Length == 2 && methodInfo.GetGenericArguments().SequenceEqual(new Type[1] { typeof(ItemData) })).ToArray();
			if (array.Length != 1)
			{
				throw new NotSupportedException("Expected one mixed-inventory rejection query.");
			}
			array[0].opcode = OpCodes.Call;
			array[0].operand = AccessTools.Method(typeof(Plugin), "RejectMixedInventory", (Type[])null, (Type[])null);
			return list;
		}

		private static bool RejectMixedInventory(IEnumerable<ItemData> items, Func<ItemData, bool> predicate)
		{
			return FoodRules.RejectMixed(items, predicate, Active);
		}

		private void OnDestroy()
		{
			ready = false;
			RemoveWhitelistAdditions();
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			if ((Object)(object)instance == (Object)(object)this)
			{
				instance = null;
			}
		}
	}
}