Decompiled source of Boat Control v0.2.0

FrizzQOL.BoatControl.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[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("FrizzQOL.BoatControl")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+bfe6d78bc99e7d35d67824480ae96ef4b5484ae2")]
[assembly: AssemblyProduct("FrizzQOL.BoatControl")]
[assembly: AssemblyTitle("FrizzQOL.BoatControl")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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;
		}
	}
}
public static class DeconstructRules
{
	public static bool? RemovalOverride(bool featureOn, bool isShip, bool playersAboard)
	{
		return RemovalOverride(featureOn, isShip, playersAboard, duringWear: false);
	}

	public static bool? RemovalOverride(bool featureOn, bool isShip, bool playersAboard, bool duringWear)
	{
		if (duringWear || !featureOn || !isShip)
		{
			return null;
		}
		return !playersAboard;
	}

	public static bool ShouldSpillCargo(bool featureOn, bool isShip, bool hammerRemove, bool blockDrop)
	{
		if (featureOn && isShip && hammerRemove)
		{
			return !blockDrop;
		}
		return false;
	}
}
public static class StorageRules
{
	public const int MaxWidth = 8;

	public static int ResolveWidth(int config, int vanilla)
	{
		if (config <= 0)
		{
			return vanilla;
		}
		if (config > 8)
		{
			return 8;
		}
		return config;
	}

	public static int ResolveHeight(int config, int vanilla)
	{
		if (config > 0)
		{
			return config;
		}
		return vanilla;
	}

	public static bool CanAddWeight(float currentWeight, float itemWeight, float maxWeight)
	{
		if (maxWeight <= 0f)
		{
			return true;
		}
		return currentWeight + itemWeight <= maxWeight;
	}
}
namespace BoatControl
{
	internal static class BoatCatalog
	{
		internal sealed class BoatConfig
		{
			public int VanillaWidth;

			public int VanillaHeight;

			public ConfigEntry<int> Width;

			public ConfigEntry<int> Height;

			public ConfigEntry<float> MaxWeight;
		}

		private static ConfigFile _config;

		private static readonly Dictionary<string, BoatConfig> Entries = new Dictionary<string, BoatConfig>();

		private static readonly Dictionary<Inventory, string> Inventories = new Dictionary<Inventory, string>();

		public static void Initialize(ConfigFile config)
		{
			_config = config;
		}

		public static void DiscoverFromScene(ZNetScene scene)
		{
			if (_config == null || (Object)(object)scene == (Object)null)
			{
				return;
			}
			if (AccessTools.Field(typeof(ZNetScene), "m_prefabs")?.GetValue(scene) is List<GameObject> { Count: not 0 } list)
			{
				int num = 0;
				foreach (GameObject item in list)
				{
					if (!((Object)(object)item == (Object)null) && !((Object)(object)item.GetComponent<Ship>() == (Object)null))
					{
						Container componentInChildren = item.GetComponentInChildren<Container>(true);
						if (!((Object)(object)componentInChildren == (Object)null) && Bind(item, componentInChildren))
						{
							num++;
						}
					}
				}
				if (num > 0)
				{
					Plugin.LogInfo($"Bound config for {num} boat chests.");
				}
			}
			else
			{
				Plugin.LogWarning("ZNetScene prefab list empty. Boat config will bind when chests load.");
			}
		}

		public static BoatConfig BindFromContainer(Container container)
		{
			Ship val = FindShip(container);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			Bind(((Component)val).gameObject, container);
			return Get(GetPrefabName(((Component)val).gameObject));
		}

		public static BoatConfig Get(string prefabName)
		{
			if (string.IsNullOrEmpty(prefabName))
			{
				return null;
			}
			if (!Entries.TryGetValue(prefabName, out var value))
			{
				return null;
			}
			return value;
		}

		public static void RegisterInventory(Inventory inventory, string prefabName)
		{
			if (inventory != null && !string.IsNullOrEmpty(prefabName))
			{
				Inventories[inventory] = prefabName;
			}
		}

		public static BoatConfig GetForInventory(Inventory inventory)
		{
			if (inventory == null)
			{
				return null;
			}
			if (!Inventories.TryGetValue(inventory, out var value))
			{
				return null;
			}
			return Get(value);
		}

		public static string GetPrefabName(GameObject obj)
		{
			if ((Object)(object)obj == (Object)null)
			{
				return null;
			}
			string name = ((Object)obj).name;
			int num = name.IndexOf("(Clone)");
			if (num < 0)
			{
				return name;
			}
			return name.Substring(0, num).Trim();
		}

		public static Ship FindShip(Container container)
		{
			if ((Object)(object)container == (Object)null)
			{
				return null;
			}
			return ((Component)container).GetComponentInParent<Ship>();
		}

		private static bool Bind(GameObject shipObj, Container chest)
		{
			string prefabName = GetPrefabName(shipObj);
			if (string.IsNullOrEmpty(prefabName) || _config == null || Entries.ContainsKey(prefabName))
			{
				return false;
			}
			int num = ReadInt(chest, "m_width", 2);
			int num2 = ReadInt(chest, "m_height", 2);
			BoatConfig value = new BoatConfig
			{
				VanillaWidth = num,
				VanillaHeight = num2,
				Width = _config.Bind<int>(prefabName, "Width", num, "Chest columns. 1-8. Values <= 0 keep vanilla."),
				Height = _config.Bind<int>(prefabName, "Height", num2, "Chest rows. Values <= 0 keep vanilla."),
				MaxWeight = _config.Bind<float>(prefabName, "MaxWeight", 0f, "Cargo weight cap. 0 or less means no cap.")
			};
			Entries[prefabName] = value;
			return true;
		}

		private static int ReadInt(object instance, string fieldName, int fallback)
		{
			FieldInfo fieldInfo = AccessTools.Field(instance.GetType(), fieldName);
			if (fieldInfo == null)
			{
				return fallback;
			}
			object value = fieldInfo.GetValue(instance);
			if (value is int)
			{
				return (int)value;
			}
			return fallback;
		}
	}
	internal static class DeconstructApplier
	{
		private static readonly Dictionary<string, bool> Originals = new Dictionary<string, bool>();

		private static readonly FieldInfo ContainerNview = AccessTools.Field(typeof(Container), "m_nview");

		private static readonly MethodInfo DropAllItems = AccessTools.Method(typeof(Container), "DropAllItems", Type.EmptyTypes, (Type[])null);

		internal static Ship FindShip(Component component)
		{
			if ((Object)(object)component == (Object)null)
			{
				return null;
			}
			Ship component2 = component.GetComponent<Ship>();
			if ((Object)(object)component2 != (Object)null)
			{
				return component2;
			}
			return component.GetComponentInChildren<Ship>(true);
		}

		internal static void ApplyPrefabs(ZNetScene scene, bool featureOn)
		{
			if ((Object)(object)scene == (Object)null || !(AccessTools.Field(typeof(ZNetScene), "m_prefabs")?.GetValue(scene) is List<GameObject> list))
			{
				return;
			}
			foreach (GameObject item in list)
			{
				if ((Object)(object)item != (Object)null && (Object)(object)item.GetComponent<Ship>() != (Object)null)
				{
					ApplyPiece(item, featureOn);
				}
			}
		}

		internal static void ApplyAllLoaded(bool featureOn)
		{
			if ((Object)(object)ZNetScene.instance != (Object)null)
			{
				ApplyPrefabs(ZNetScene.instance, featureOn);
			}
			Ship[] array = Object.FindObjectsByType<Ship>((FindObjectsSortMode)0);
			if (array == null)
			{
				return;
			}
			Ship[] array2 = array;
			foreach (Ship val in array2)
			{
				if ((Object)(object)val != (Object)null)
				{
					ApplyPiece(((Component)val).gameObject, featureOn);
				}
			}
		}

		internal static void ApplyPiece(GameObject shipObject, bool featureOn)
		{
			if ((Object)(object)shipObject == (Object)null)
			{
				return;
			}
			Piece component = shipObject.GetComponent<Piece>();
			if ((Object)(object)component == (Object)null)
			{
				return;
			}
			string prefabName = BoatCatalog.GetPrefabName(shipObject);
			if (!string.IsNullOrEmpty(prefabName))
			{
				if (!Originals.ContainsKey(prefabName))
				{
					Originals[prefabName] = component.m_canBeRemoved;
				}
				component.m_canBeRemoved = featureOn || Originals[prefabName];
			}
		}

		internal static bool AllowDestroy(WearNTear wear, bool featureOn, bool hammerRemove, bool blockDrop)
		{
			if (!featureOn || !hammerRemove || blockDrop)
			{
				return true;
			}
			Ship val = FindShip((Component)(object)wear);
			if (!DeconstructRules.ShouldSpillCargo(featureOn, (Object)(object)val != (Object)null, hammerRemove, blockDrop))
			{
				return true;
			}
			if (DropAllItems == null)
			{
				Plugin.LogError("Container.DropAllItems() was not found. Ship was not removed.");
				return false;
			}
			Container[] componentsInChildren = ((Component)val).GetComponentsInChildren<Container>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				if (!TryDrop(componentsInChildren[i]))
				{
					return false;
				}
			}
			return true;
		}

		private static bool TryDrop(Container chest)
		{
			if ((Object)(object)chest == (Object)null || chest.GetInventory() == null)
			{
				return true;
			}
			object? obj = ContainerNview?.GetValue(chest);
			ZNetView val = (ZNetView)((obj is ZNetView) ? obj : null);
			if ((Object)(object)val == (Object)null || !val.IsValid() || !val.IsOwner())
			{
				return true;
			}
			try
			{
				DropAllItems.Invoke(chest, null);
				return true;
			}
			catch (Exception ex)
			{
				Exception ex2 = ((ex is TargetInvocationException { InnerException: not null } ex3) ? ex3.InnerException : ex);
				Plugin.LogError("Cargo drop failed. Ship was not removed: " + ex2.Message);
				return false;
			}
		}
	}
	[HarmonyPatch(typeof(Ship), "Awake")]
	internal static class ShipAwakePatch
	{
		private static void Postfix(Ship __instance)
		{
			if ((Object)(object)__instance != (Object)null)
			{
				DeconstructApplier.ApplyPiece(((Component)__instance).gameObject, Plugin.FeatureOn());
			}
		}
	}
	[HarmonyPatch(typeof(WearNTear), "UpdateWear")]
	internal static class WearNTearUpdateWearPatch
	{
		[ThreadStatic]
		private static int s_depth;

		internal static bool IsActive => s_depth > 0;

		private static void Prefix()
		{
			s_depth++;
		}

		private static Exception Finalizer(Exception __exception)
		{
			if (s_depth > 0)
			{
				s_depth--;
			}
			return __exception;
		}
	}
	[HarmonyPatch(typeof(WearNTear), "RPC_Remove")]
	internal static class WearNTearRpcRemovePatch
	{
		[ThreadStatic]
		private static int s_depth;

		internal static bool IsActive => s_depth > 0;

		private static void Prefix()
		{
			s_depth++;
		}

		private static Exception Finalizer(Exception __exception)
		{
			if (s_depth > 0)
			{
				s_depth--;
			}
			return __exception;
		}
	}
	[HarmonyPatch(typeof(Piece), "CanBeRemoved")]
	internal static class PieceCanBeRemovedPatch
	{
		private static void Postfix(Piece __instance, ref bool __result)
		{
			if (!WearNTearUpdateWearPatch.IsActive)
			{
				Ship val = DeconstructApplier.FindShip((Component)(object)__instance);
				bool playersAboard = (Object)(object)val != (Object)null && !val.CanBeRemoved();
				bool? flag = DeconstructRules.RemovalOverride(Plugin.FeatureOn(), (Object)(object)val != (Object)null, playersAboard);
				if (flag.HasValue)
				{
					__result = flag.Value;
				}
			}
		}
	}
	[HarmonyPatch(typeof(WearNTear), "Destroy")]
	internal static class WearNTearDestroyPatch
	{
		private static bool Prefix(WearNTear __instance, bool blockDrop)
		{
			return DeconstructApplier.AllowDestroy(__instance, Plugin.FeatureOn(), WearNTearRpcRemovePatch.IsActive, blockDrop);
		}
	}
	[BepInPlugin("com.frizzqol.boatcontrol", "FrizzQOL Boat Control", "0.2.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.frizzqol.boatcontrol";

		public const string PluginName = "FrizzQOL Boat Control";

		public const string PluginVersion = "0.2.0";

		private Harmony _harmony;

		internal static Plugin Instance { get; private set; }

		internal static ConfigEntry<bool> HammerDeconstruct { get; private set; }

		private void Awake()
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			Instance = this;
			HammerDeconstruct = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "HammerDeconstruct", true, "Middle-click with the hammer deconstructs a ship. Cargo drops on the ground, then the boat materials return. Someone standing on the ship blocks it.");
			BoatCatalog.Initialize(((BaseUnityPlugin)this).Config);
			((BaseUnityPlugin)this).Config.SettingChanged += delegate
			{
				StorageApplier.ApplyAllLoaded();
				DeconstructApplier.ApplyAllLoaded(FeatureOn());
			};
			_harmony = new Harmony("com.frizzqol.boatcontrol");
			try
			{
				_harmony.PatchAll();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"FrizzQOL Boat Control 0.2.0 loaded");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Harmony patch failed: " + ex.Message));
			}
		}

		internal static bool FeatureOn()
		{
			if (HammerDeconstruct != null)
			{
				return HammerDeconstruct.Value;
			}
			return false;
		}

		internal static void LogInfo(string message)
		{
			if ((Object)(object)Instance != (Object)null)
			{
				((BaseUnityPlugin)Instance).Logger.LogInfo((object)message);
			}
		}

		internal static void LogWarning(string message)
		{
			if ((Object)(object)Instance != (Object)null)
			{
				((BaseUnityPlugin)Instance).Logger.LogWarning((object)message);
			}
		}

		internal static void LogError(string message)
		{
			if ((Object)(object)Instance != (Object)null)
			{
				((BaseUnityPlugin)Instance).Logger.LogError((object)message);
			}
		}
	}
	internal static class StorageApplier
	{
		private static readonly FieldInfo ContainerWidth = AccessTools.Field(typeof(Container), "m_width");

		private static readonly FieldInfo ContainerHeight = AccessTools.Field(typeof(Container), "m_height");

		private static readonly FieldInfo InventoryWidth = AccessTools.Field(typeof(Inventory), "m_width");

		private static readonly FieldInfo InventoryHeight = AccessTools.Field(typeof(Inventory), "m_height");

		public static void Apply(Container container)
		{
			if ((Object)(object)container == (Object)null)
			{
				return;
			}
			BoatCatalog.BoatConfig boatConfig = BoatCatalog.BindFromContainer(container);
			if (boatConfig == null)
			{
				return;
			}
			int num = StorageRules.ResolveWidth(boatConfig.Width.Value, boatConfig.VanillaWidth);
			int num2 = StorageRules.ResolveHeight(boatConfig.Height.Value, boatConfig.VanillaHeight);
			if (ContainerWidth != null)
			{
				ContainerWidth.SetValue(container, num);
			}
			if (ContainerHeight != null)
			{
				ContainerHeight.SetValue(container, num2);
			}
			Inventory inventory = container.GetInventory();
			if (inventory != null)
			{
				if (InventoryWidth != null)
				{
					InventoryWidth.SetValue(inventory, num);
				}
				inventory.SetHeight(num2);
				if (InventoryHeight != null)
				{
					InventoryHeight.SetValue(inventory, num2);
				}
				Ship val = BoatCatalog.FindShip(container);
				string prefabName = (((Object)(object)val != (Object)null) ? BoatCatalog.GetPrefabName(((Component)val).gameObject) : null);
				BoatCatalog.RegisterInventory(inventory, prefabName);
			}
		}

		public static void ApplyAllLoaded()
		{
			Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0);
			if (array == null)
			{
				return;
			}
			Container[] array2 = array;
			foreach (Container container in array2)
			{
				if ((Object)(object)BoatCatalog.FindShip(container) != (Object)null)
				{
					Apply(container);
				}
			}
		}
	}
	[HarmonyPatch(typeof(ZNetScene), "Awake")]
	internal static class ZNetSceneAwakePatch
	{
		private static void Postfix(ZNetScene __instance)
		{
			BoatCatalog.DiscoverFromScene(__instance);
			DeconstructApplier.ApplyPrefabs(__instance, Plugin.FeatureOn());
		}
	}
	[HarmonyPatch(typeof(Container), "Awake")]
	internal static class ContainerAwakePatch
	{
		private static void Postfix(Container __instance)
		{
			StorageApplier.Apply(__instance);
		}
	}
	[HarmonyPatch(typeof(Inventory), "CanAddItem", new Type[]
	{
		typeof(ItemData),
		typeof(int)
	})]
	internal static class CanAddItemPatch
	{
		private static bool Prefix(Inventory __instance, ItemData __0, int __1, ref bool __result)
		{
			if (WeightGate.AllowsWeight(__instance, WeightGate.ItemWeight(__0, __1)))
			{
				return true;
			}
			__result = false;
			return false;
		}
	}
	[HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemData) })]
	internal static class AddItemPatch
	{
		private static bool Prefix(Inventory __instance, ItemData __0)
		{
			return WeightGate.AllowsWeight(__instance, WeightGate.ItemWeight(__0, __0?.m_stack ?? 0));
		}
	}
	internal static class WeightGate
	{
		public static bool AllowsWeight(Inventory inventory, float itemWeight)
		{
			BoatCatalog.BoatConfig forInventory = BoatCatalog.GetForInventory(inventory);
			if (forInventory == null)
			{
				return true;
			}
			return StorageRules.CanAddWeight(inventory.GetTotalWeight(), itemWeight, forInventory.MaxWeight.Value);
		}

		public static float ItemWeight(ItemData item, int stack)
		{
			if (item == null || item.m_shared == null)
			{
				return 0f;
			}
			int num = ((stack <= 0) ? 1 : stack);
			return item.m_shared.m_weight * (float)num;
		}
	}
}