Decompiled source of Hearthmend v1.0.3

plugins/Hearthmend.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Jotunn;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyCompany("blackhearthx")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Station auto-repair for the homestead. Each craft station mends what it would fix with the hammer, ships and carts included.")]
[assembly: AssemblyFileVersion("1.0.3.0")]
[assembly: AssemblyInformationalVersion("1.0.3+c17d3fa031bce2e2b809047b47945fe64757c5a7")]
[assembly: AssemblyProduct("Hearthmend")]
[assembly: AssemblyTitle("Hearthmend")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.3.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;
		}
	}
}
namespace Hearthmend
{
	internal static class ModLocalization
	{
		internal static void Register()
		{
			CustomLocalization localization = LocalizationManager.Instance.GetLocalization();
			int num = 0;
			string[] array = new string[3] { "English", "Portuguese_Brazilian", "Portuguese_European" };
			foreach (string language in array)
			{
				if (TryLoad(localization, language))
				{
					num++;
				}
			}
			if (num == 0)
			{
				string text = "English";
				localization.AddTranslation(ref text, FallbackEnglish());
				Logger.LogWarning((object)"Hearthmend: Translations folder missing, using the English fallback");
			}
		}

		private static Dictionary<string, string> FallbackEnglish()
		{
			return new Dictionary<string, string>
			{
				{ "hearthmend_hover", "\n[<color=yellow><b>Hold $KEY_Use</b></color>] Hearthmend: {0}" },
				{ "hearthmend_on", "<color=#55FF55>on</color>" },
				{ "hearthmend_off", "<color=#FF5555>off</color>" },
				{ "hearthmend_toggle_on", "Hearthmend is watching this station" },
				{ "hearthmend_toggle_off", "Hearthmend won't touch this station" },
				{ "hearthmend_repaired", "Mended {0} pieces" },
				{ "hearthmend_morning", "Woke up to {0} pieces mended" }
			};
		}

		private static bool TryLoad(CustomLocalization loc, string language)
		{
			try
			{
				string text = FindPath(language);
				if (string.IsNullOrEmpty(text) || !File.Exists(text))
				{
					return false;
				}
				loc.AddJsonFile(language, File.ReadAllText(text));
				return true;
			}
			catch (Exception ex)
			{
				Logger.LogWarning((object)("Hearthmend: failed loading " + language + ": " + ex.Message));
				return false;
			}
		}

		private static string FindPath(string language)
		{
			string path = Path.Combine("Translations", language, "hearthmend.json");
			string text = Path.Combine(Path.GetDirectoryName(typeof(HearthmendPlugin).Assembly.Location) ?? "", path);
			if (File.Exists(text))
			{
				return text;
			}
			string text2 = Path.Combine(Directory.GetCurrentDirectory(), path);
			if (!File.Exists(text2))
			{
				return null;
			}
			return text2;
		}

		internal static string L(string token, params object[] args)
		{
			string text = ((Localization.instance != null) ? Localization.instance.Localize("$" + token) : ("$" + token));
			if (args == null || args.Length == 0)
			{
				return text;
			}
			try
			{
				return string.Format(text, args);
			}
			catch
			{
				return text;
			}
		}
	}
	[BepInPlugin("com.blackhearthx.hearthmend", "Hearthmend", "1.0.3")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public class HearthmendPlugin : BaseUnityPlugin
	{
		public const string PluginGUID = "com.blackhearthx.hearthmend";

		public const string PluginName = "Hearthmend";

		public const string PluginVersion = "1.0.3";

		private Harmony _harmony;

		internal static int PieceMask { get; private set; }

		internal static HearthmendPlugin Instance { get; private set; }

		private void Awake()
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Expected O, but got Unknown
			Instance = this;
			PieceMask = LayerMask.GetMask(new string[6] { "Default", "static_solid", "Default_small", "piece", "piece_nonsolid", "vehicle" });
			PluginConfig.Bind(((BaseUnityPlugin)this).Config);
			ModLocalization.Register();
			PrefabManager.OnVanillaPrefabsAvailable += OnVanillaPrefabsAvailable;
			_harmony = new Harmony("com.blackhearthx.hearthmend");
			_harmony.PatchAll();
			Logger.LogInfo((object)"Hearthmend 1.0.3 loaded");
		}

		private void Update()
		{
			RepairService.UpdateTimerLoop();
		}

		private static void OnVanillaPrefabsAvailable()
		{
			PrefabManager.OnVanillaPrefabsAvailable -= OnVanillaPrefabsAvailable;
			RepairService.RefreshBuildStationCatalog();
		}

		private void OnDestroy()
		{
			PrefabManager.OnVanillaPrefabsAvailable -= OnVanillaPrefabsAvailable;
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class PluginConfig
	{
		internal static ConfigEntry<bool> ModEnabled;

		internal static ConfigEntry<float> RepairRadius;

		internal static ConfigEntry<float> MendEvery;

		internal static ConfigEntry<bool> MendOnWake;

		internal static ConfigEntry<bool> AllowRepairOther;

		internal static ConfigEntry<bool> RespectWards;

		internal static ConfigEntry<bool> ShowNotification;

		internal static void Bind(ConfigFile cfg)
		{
			ModEnabled = cfg.Bind<bool>("1. General", "Mod Enabled", true, "Turn Hearthmend on or off for this profile.");
			RepairRadius = cfg.Bind<float>("2. Repair", "Repair Radius", 20f, "How far from a watching station (workbench, forge, stonecutter and so on) pieces get mended.");
			MendEvery = cfg.Bind<float>("2. Repair", "Mend Every (s)", 30f, "While you are near a watching station, it mends the damage around it this often. 0 turns daytime mending off.");
			MendOnWake = cfg.Bind<bool>("2. Repair", "Mend On Wake", true, "Every station watching near you also mends when you wake up from sleep.");
			AllowRepairOther = cfg.Bind<bool>("2. Repair", "Allow Repair Other", true, "Also mend pieces built by other players.");
			RespectWards = cfg.Bind<bool>("2. Repair", "Respect Wards", true, "Skip pieces inside wards you are not allowed to build in.");
			ShowNotification = cfg.Bind<bool>("3. UI", "Show Notification", true, "Show a small HUD note when something gets mended.");
		}
	}
	internal static class RepairService
	{
		internal const string ZdoKey = "BHX_Hearthmend";

		private const float HoldRequiredSeconds = 0.6f;

		private const float TimerScanPeriod = 0.25f;

		private const float MendGapSeconds = 0.15f;

		private const float MaxWaveSeconds = 6f;

		private const float TimerJitterSeconds = 3f;

		private static readonly List<WearNTear> PendingMends = new List<WearNTear>(256);

		private static readonly FieldInfo AllStationsField = AccessTools.Field(typeof(CraftingStation), "m_allStations");

		private static readonly Collider[] OverlapBuffer = (Collider[])(object)new Collider[4096];

		private static bool _warnedBufferFull;

		private static readonly HashSet<Piece> ProcessedPieces = new HashSet<Piece>();

		private static readonly Dictionary<CraftingStation, float> StationTimers = new Dictionary<CraftingStation, float>(32);

		private static readonly HashSet<string> BuildStationNames = new HashSet<string>();

		private static float _nextLoopCheckTime;

		private static float _holdTimer;

		private static bool _holdExecuted;

		internal static void RefreshBuildStationCatalog()
		{
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null || instance.m_prefabs == null)
			{
				return;
			}
			BuildStationNames.Clear();
			for (int i = 0; i < instance.m_prefabs.Count; i++)
			{
				GameObject val = instance.m_prefabs[i];
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Piece component = val.GetComponent<Piece>();
				if (!((Object)(object)component == (Object)null) && !((Object)(object)component.m_craftingStation == (Object)null))
				{
					string name = component.m_craftingStation.m_name;
					if (!string.IsNullOrEmpty(name))
					{
						BuildStationNames.Add(name);
					}
				}
			}
			Logger.LogInfo((object)$"Hearthmend: {BuildStationNames.Count} build station type(s) can watch");
		}

		internal static bool IsHearthmendStation(CraftingStation station)
		{
			if ((Object)(object)station == (Object)null)
			{
				return false;
			}
			if (BuildStationNames.Count == 0)
			{
				RefreshBuildStationCatalog();
			}
			return BuildStationNames.Contains(station.m_name);
		}

		internal static bool PieceBelongsToStation(CraftingStation station, Piece piece)
		{
			if ((Object)(object)station == (Object)null || (Object)(object)piece == (Object)null)
			{
				return false;
			}
			if ((Object)(object)piece.m_craftingStation == (Object)null)
			{
				return true;
			}
			return piece.m_craftingStation.m_name == station.m_name;
		}

		internal static bool GetEnabled(ZNetView nview)
		{
			if ((Object)(object)nview != (Object)null && nview.IsValid())
			{
				return nview.GetZDO().GetBool("BHX_Hearthmend", false);
			}
			return false;
		}

		internal static void SetEnabled(ZNetView nview, bool enabled)
		{
			if (!((Object)(object)nview == (Object)null) && nview.IsValid())
			{
				nview.ClaimOwnership();
				nview.GetZDO().Set("BHX_Hearthmend", enabled);
			}
		}

		private static List<CraftingStation> GetStationList()
		{
			return AllStationsField?.GetValue(null) as List<CraftingStation>;
		}

		internal static void ShowMessage(MessageType type, string message)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Player.m_localPlayer != (Object)null)
			{
				((Character)Player.m_localPlayer).Message(type, message, 0, (Sprite)null, false);
				return;
			}
			MessageHud instance = MessageHud.instance;
			if (instance != null)
			{
				instance.ShowMessage(type, message, 0, (Sprite)null, false, true);
			}
		}

		internal static void UpdateTimerLoop()
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			if (!PluginConfig.ModEnabled.Value || PluginConfig.MendEvery.Value <= 0f || Time.time < _nextLoopCheckTime)
			{
				return;
			}
			_nextLoopCheckTime = Time.time + 0.25f;
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			List<CraftingStation> stationList = GetStationList();
			if (stationList == null || stationList.Count == 0)
			{
				return;
			}
			Vector3 position = ((Component)localPlayer).transform.position;
			float num = PluginConfig.RepairRadius.Value + 35f;
			float num2 = num * num;
			float time = Time.time;
			for (int i = 0; i < stationList.Count; i++)
			{
				CraftingStation val = stationList[i];
				if (!IsHearthmendStation(val))
				{
					continue;
				}
				Vector3 val2 = ((Component)val).transform.position - position;
				if (!(((Vector3)(ref val2)).sqrMagnitude > num2))
				{
					ZNetView component = ((Component)val).GetComponent<ZNetView>();
					if (!((Object)(object)component == (Object)null) && component.IsValid() && GetEnabled(component) && (!StationTimers.TryGetValue(val, out var value) || !(time < value)))
					{
						ScheduleNext(val, time);
						PerformStationRepair(val, suppressMessage: false);
					}
				}
			}
		}

		private static void ScheduleNext(CraftingStation station, float now)
		{
			StationTimers[station] = now + PluginConfig.MendEvery.Value + Random.Range(0f, 3f);
		}

		internal static void TriggerMorningRepair()
		{
			//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_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			if (!PluginConfig.ModEnabled.Value || !PluginConfig.MendOnWake.Value)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			List<CraftingStation> stationList = GetStationList();
			if (stationList == null || stationList.Count == 0)
			{
				return;
			}
			Vector3 position = ((Component)localPlayer).transform.position;
			float num = PluginConfig.RepairRadius.Value + 50f;
			float num2 = num * num;
			int num3 = 0;
			int num4 = 0;
			float time = Time.time;
			ProcessedPieces.Clear();
			for (int i = 0; i < stationList.Count; i++)
			{
				CraftingStation val = stationList[i];
				if (!IsHearthmendStation(val))
				{
					continue;
				}
				Vector3 val2 = ((Component)val).transform.position - position;
				if (!(((Vector3)(ref val2)).sqrMagnitude > num2))
				{
					ZNetView component = ((Component)val).GetComponent<ZNetView>();
					if (!((Object)(object)component == (Object)null) && component.IsValid() && GetEnabled(component))
					{
						num4++;
						ScheduleNext(val, time);
						num3 += PerformStationRepair(val, suppressMessage: true, shareProcessed: true);
					}
				}
			}
			Logger.LogInfo((object)$"Hearthmend: woke up, {num4} station(s) watching nearby, {num3} piece(s) to mend");
			if (num3 > 0 && PluginConfig.ShowNotification.Value)
			{
				ShowMessage((MessageType)1, ModLocalization.L("hearthmend_morning", num3));
			}
		}

		internal static int PerformStationRepair(CraftingStation station, bool suppressMessage, bool shareProcessed = false)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)station == (Object)null)
			{
				return 0;
			}
			float value = PluginConfig.RepairRadius.Value;
			int num = Physics.OverlapSphereNonAlloc(((Component)station).transform.position, value, OverlapBuffer, HearthmendPlugin.PieceMask);
			if (num == 0)
			{
				return 0;
			}
			if (num == OverlapBuffer.Length && !_warnedBufferFull)
			{
				_warnedBufferFull = true;
				Logger.LogWarning((object)$"Hearthmend: {OverlapBuffer.Length} colliders around {station.m_name}, some pieces may be skipped. Try a smaller Repair Radius.");
			}
			PendingMends.Clear();
			if (!shareProcessed)
			{
				ProcessedPieces.Clear();
			}
			bool value2 = PluginConfig.AllowRepairOther.Value;
			bool value3 = PluginConfig.RespectWards.Value;
			for (int i = 0; i < num; i++)
			{
				Collider val = OverlapBuffer[i];
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Piece componentInParent = ((Component)val).GetComponentInParent<Piece>();
				if (!((Object)(object)componentInParent == (Object)null) && PieceBelongsToStation(station, componentInParent) && ProcessedPieces.Add(componentInParent) && (value2 || componentInParent.IsCreator()) && (!value3 || PrivateArea.CheckAccess(((Component)componentInParent).transform.position, 0f, false, true)))
				{
					WearNTear component = ((Component)componentInParent).GetComponent<WearNTear>();
					if (!((Object)(object)component == (Object)null) && !(component.GetHealthPercentage() >= 1f))
					{
						PendingMends.Add(component);
					}
				}
			}
			int count = PendingMends.Count;
			if (count == 0)
			{
				return 0;
			}
			((MonoBehaviour)HearthmendPlugin.Instance).StartCoroutine(MendInTurn(PendingMends.ToArray()));
			PendingMends.Clear();
			Logger.LogInfo((object)$"Hearthmend: {station.m_name} is mending {count} piece(s)");
			if (!suppressMessage && count > 0 && PluginConfig.ShowNotification.Value && (Object)(object)Player.m_localPlayer != (Object)null && Vector3.Distance(((Component)station).transform.position, ((Component)Player.m_localPlayer).transform.position) <= value)
			{
				ShowMessage((MessageType)1, ModLocalization.L("hearthmend_repaired", count));
			}
			return count;
		}

		private static IEnumerator MendInTurn(WearNTear[] batch)
		{
			WaitForSeconds wait = new WaitForSeconds(Mathf.Min(0.15f, 6f / (float)batch.Length));
			for (int i = 0; i < batch.Length; i++)
			{
				WearNTear val = batch[i];
				if ((Object)(object)val != (Object)null && val.Repair())
				{
					PlayMendEffect(val);
				}
				if (i < batch.Length - 1)
				{
					yield return wait;
				}
			}
		}

		private static void PlayMendEffect(WearNTear wear)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			Piece component = ((Component)wear).GetComponent<Piece>();
			if (!((Object)(object)component == (Object)null) && component.m_placeEffect != null && component.m_placeEffect.HasEffects())
			{
				Transform transform = ((Component)component).transform;
				component.m_placeEffect.Create(transform.position, transform.rotation, (Transform)null, 1f, -1, default(ZDOID));
			}
		}

		internal static void AppendHoverHint(CraftingStation station, ref string result)
		{
			if (PluginConfig.ModEnabled.Value && IsHearthmendStation(station))
			{
				ZNetView component = ((Component)station).GetComponent<ZNetView>();
				if (!((Object)(object)component == (Object)null) && component.IsValid())
				{
					string text = (GetEnabled(component) ? ModLocalization.L("hearthmend_on") : ModLocalization.L("hearthmend_off"));
					string text2 = ModLocalization.L("hearthmend_hover", text);
					result += ((Localization.instance != null) ? Localization.instance.Localize(text2) : text2);
				}
			}
		}

		internal static void TickHoldToggle(Player player)
		{
			if ((Object)(object)player != (Object)(object)Player.m_localPlayer || !PluginConfig.ModEnabled.Value)
			{
				return;
			}
			if (!ZInput.GetButton("Use"))
			{
				_holdTimer = 0f;
				_holdExecuted = false;
				return;
			}
			GameObject hoverObject = ((Humanoid)player).GetHoverObject();
			if ((Object)(object)hoverObject == (Object)null)
			{
				return;
			}
			CraftingStation componentInParent = hoverObject.GetComponentInParent<CraftingStation>();
			if (!IsHearthmendStation(componentInParent))
			{
				return;
			}
			ZNetView component = ((Component)componentInParent).GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || !component.IsValid())
			{
				return;
			}
			_holdTimer += Time.deltaTime;
			if (!(_holdTimer < 0.6f) && !_holdExecuted)
			{
				_holdExecuted = true;
				bool flag = !GetEnabled(component);
				SetEnabled(component, flag);
				ShowMessage((MessageType)2, flag ? ModLocalization.L("hearthmend_toggle_on") : ModLocalization.L("hearthmend_toggle_off"));
				if (flag)
				{
					ScheduleNext(componentInParent, Time.time);
					PerformStationRepair(componentInParent, suppressMessage: false);
				}
			}
		}
	}
	[HarmonyPatch(typeof(CraftingStation), "GetHoverText")]
	internal static class CraftingStation_GetHoverText_Patch
	{
		private static void Postfix(CraftingStation __instance, ref string __result)
		{
			RepairService.AppendHoverHint(__instance, ref __result);
		}
	}
	[HarmonyPatch(typeof(Player), "Update")]
	internal static class Player_Update_HoldToggle_Patch
	{
		private static void Postfix(Player __instance)
		{
			RepairService.TickHoldToggle(__instance);
		}
	}
	[HarmonyPatch(typeof(Player), "SetSleeping")]
	internal static class Player_SetSleeping_Patch
	{
		private static void Postfix(Player __instance, bool sleep)
		{
			if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && !sleep)
			{
				RepairService.TriggerMorningRepair();
			}
		}
	}
}