Decompiled source of HackAndSap v0.1.0

plugins/HackAndSap.dll

Decompiled 13 hours 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 Jotunn.Extensions;
using Jotunn.Managers;
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("HackAndSap")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+afdf9b0d3556fc85b43868f55bd7f2664ea710e9")]
[assembly: AssemblyProduct("HackAndSap")]
[assembly: AssemblyTitle("HackAndSap")]
[assembly: AssemblyVersion("1.0.0.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 HackAndSap
{
	internal static class AxeUtil
	{
		private const float CloseInteractionDistance = 1.1f;

		public static ItemData GetEquippedAxe(Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return null;
			}
			ItemData currentWeapon = ((Humanoid)player).GetCurrentWeapon();
			if (!IsUsableAxe(currentWeapon))
			{
				return null;
			}
			return currentWeapon;
		}

		public static bool IsUsableAxe(ItemData item)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Invalid comparison between Unknown and I4
			if (item?.m_shared == null)
			{
				return false;
			}
			if ((int)item.m_shared.m_skillType != 7)
			{
				return false;
			}
			if (item.m_shared.m_damages.m_chop <= 0f)
			{
				return false;
			}
			if (!item.HavePrimaryAttack())
			{
				return false;
			}
			if (item.m_shared.m_useDurability)
			{
				return item.m_durability > 0f;
			}
			return true;
		}

		public static float GetInteractionReach()
		{
			return Math.Min(1.1f, Math.Max(0.5f, ModConfig.MaximumDistance.Value));
		}
	}
	public sealed class CuttingInteraction : MonoBehaviour
	{
		private static readonly Collider[] NearbyColliders = (Collider[])(object)new Collider[64];

		private TreeBase _tree;

		private TreeLog _log;

		private ZNetView _nview;

		public ZNetView NetView => _nview;

		private void Awake()
		{
			_tree = ((Component)this).GetComponent<TreeBase>();
			_log = ((Component)this).GetComponent<TreeLog>();
			_nview = ((Component)this).GetComponent<ZNetView>();
		}

		public bool IsAvailable()
		{
			if (!ModConfig.Enabled.Value || (Object)(object)_nview == (Object)null || !_nview.IsValid())
			{
				return false;
			}
			if ((Object)(object)_tree != (Object)null)
			{
				if (ModConfig.EnableTreeInteraction.Value)
				{
					return (Object)(object)_tree.m_logPrefab != (Object)null;
				}
				return false;
			}
			if ((Object)(object)_log != (Object)null)
			{
				return ModConfig.EnableLogInteraction.Value;
			}
			return false;
		}

		public bool IsColliderNearby(Player player, Collider collider)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null || (Object)(object)collider == (Object)null || !collider.enabled)
			{
				return false;
			}
			int num = Physics.OverlapSphereNonAlloc(((Character)player).GetCenterPoint(), AxeUtil.GetInteractionReach(), NearbyColliders, -1, (QueryTriggerInteraction)2);
			for (int i = 0; i < num; i++)
			{
				if ((Object)(object)NearbyColliders[i] == (Object)(object)collider)
				{
					return true;
				}
			}
			return false;
		}

		public bool TryGetCrosshairPoint(Player player, Collider collider, out Vector3 point)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: 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_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val;
			if (!((Object)(object)collider != (Object)null))
			{
				val = ((Component)this).transform.position;
			}
			else
			{
				Bounds bounds = collider.bounds;
				val = ((Bounds)(ref bounds)).center;
			}
			point = val;
			if ((Object)(object)player == (Object)null || (Object)(object)collider == (Object)null || !collider.enabled || (Object)(object)GameCamera.instance == (Object)null)
			{
				return false;
			}
			Transform transform = ((Component)GameCamera.instance).transform;
			RaycastHit val2 = default(RaycastHit);
			if (!collider.Raycast(new Ray(transform.position, transform.forward), ref val2, 50f))
			{
				return false;
			}
			point = ((RaycastHit)(ref val2)).point;
			return true;
		}

		public string GetHoverName(Collider collider)
		{
			if (!IsAvailable() || !IsColliderNearby(Player.m_localPlayer, collider))
			{
				return string.Empty;
			}
			if (!((Object)(object)_tree != (Object)null))
			{
				return "Felled log";
			}
			return "Tree";
		}

		public string GetHoverText(Collider collider)
		{
			if (!IsAvailable() || !IsColliderNearby(Player.m_localPlayer, collider))
			{
				return string.Empty;
			}
			Player localPlayer = Player.m_localPlayer;
			string text = (CuttingSession.IsTarget(this) ? "Stop hacking" : ((AxeUtil.GetEquippedAxe(localPlayer) != null) ? "Start hacking" : "Equip an axe"));
			return Localization.instance.Localize("[<color=yellow><b>$KEY_Use</b></color>] " + text);
		}

		public bool Interact(Humanoid user, bool hold, bool alt, Collider collider)
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			if (hold || alt || !IsAvailable())
			{
				return false;
			}
			Player val = (Player)(object)((user is Player) ? user : null);
			if (val == null || (Object)(object)val != (Object)(object)Player.m_localPlayer)
			{
				return false;
			}
			if (CuttingSession.IsTarget(this))
			{
				CuttingSession.Cancel(notify: false);
				return false;
			}
			ItemData equippedAxe = AxeUtil.GetEquippedAxe(val);
			if (equippedAxe == null)
			{
				((Character)val).Message((MessageType)2, "Equip an axe to start hacking", 0, (Sprite)null);
				return false;
			}
			if (!IsColliderNearby(val, collider))
			{
				((Character)val).Message((MessageType)2, "Move closer to start hacking", 0, (Sprite)null);
				return false;
			}
			TryGetCrosshairPoint(val, collider, out var point);
			CuttingSession.Begin(this, collider, point, val, equippedAxe);
			return false;
		}
	}
	internal static class CuttingSession
	{
		private static CuttingInteraction _target;

		private static Collider _collider;

		private static Vector3 _localSurfacePoint;

		private static Player _player;

		private static ItemData _axe;

		private static bool _armedForCancel;

		private static float _nextAttackAttempt;

		private static float _firstFailedAttempt = -1f;

		public static bool Active => (Object)(object)_target != (Object)null;

		public static bool IsTarget(CuttingInteraction target)
		{
			if (Active)
			{
				return (Object)(object)_target == (Object)(object)target;
			}
			return false;
		}

		public static void Begin(CuttingInteraction target, Collider collider, Vector3 surfacePoint, Player player, ItemData axe)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)target == (Object)null) && !((Object)(object)collider == (Object)null) && !((Object)(object)player == (Object)null) && axe != null)
			{
				if (Active)
				{
					Cancel(notify: false);
				}
				_target = target;
				_collider = collider;
				_localSurfacePoint = ((Component)collider).transform.InverseTransformPoint(surfacePoint);
				_player = player;
				_axe = axe;
				_armedForCancel = false;
				_nextAttackAttempt = 0f;
				_firstFailedAttempt = -1f;
				ModLog.Info("Started hacking '" + ((Object)((Component)target).gameObject).name + "' with '" + axe.m_shared.m_name + "'.");
			}
		}

		public static void Tick(float deltaTime)
		{
			if (!Active)
			{
				return;
			}
			if (!StillValid())
			{
				Cancel(notify: false);
				return;
			}
			if (!_armedForCancel)
			{
				if (!ZInput.GetButton("Use"))
				{
					_armedForCancel = true;
				}
			}
			else if (ZInput.GetButtonDown("Use"))
			{
				Cancel(notify: false);
				return;
			}
			if (ZInput.GetButtonDown("Attack"))
			{
				Cancel(notify: false);
			}
			else
			{
				if (((Character)_player).InAttack() || Time.time < _nextAttackAttempt)
				{
					return;
				}
				FaceTarget();
				bool num = ((Character)_player).StartAttack((Character)null, false);
				_nextAttackAttempt = Time.time + 0.12f;
				if (num)
				{
					_firstFailedAttempt = -1f;
					return;
				}
				if (_firstFailedAttempt < 0f)
				{
					_firstFailedAttempt = Time.time;
				}
				if (Time.time - _firstFailedAttempt > 1f)
				{
					((Character)_player).Message((MessageType)2, "Unable to continue hacking", 0, (Sprite)null);
					Cancel(notify: false);
				}
			}
		}

		private static Vector3 GetSurfacePoint()
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			return ((Component)_collider).transform.TransformPoint(_localSurfacePoint);
		}

		private static bool StillValid()
		{
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_target == (Object)null || (Object)(object)_collider == (Object)null || (Object)(object)_player == (Object)null || _axe == null)
			{
				return false;
			}
			if ((Object)(object)_player != (Object)(object)Player.m_localPlayer || ((Character)_player).IsDead())
			{
				return false;
			}
			if (!_target.IsAvailable())
			{
				return false;
			}
			if ((Object)(object)_target.NetView == (Object)null || !_target.NetView.IsValid())
			{
				return false;
			}
			if (((Humanoid)_player).GetCurrentWeapon() != _axe || !AxeUtil.IsUsableAxe(_axe))
			{
				return false;
			}
			if (((Character)_player).InDodge() || ((Character)_player).IsStaggering() || ((Character)_player).IsKnockedBack())
			{
				return false;
			}
			Vector3 moveDir = ((Character)_player).GetMoveDir();
			if (((Vector3)(ref moveDir)).sqrMagnitude > 0.01f)
			{
				return false;
			}
			return _target.IsColliderNearby(_player, _collider);
		}

		private static void FaceTarget()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = GetSurfacePoint() - ((Component)_player).transform.position;
			val.y = 0f;
			if (((Vector3)(ref val)).sqrMagnitude > 0.001f)
			{
				((Character)_player).SetLookDir(((Vector3)(ref val)).normalized, 0.08f);
			}
		}

		public static void Cancel(bool notify)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			if (Active)
			{
				if (notify && (Object)(object)_player != (Object)null)
				{
					((Character)_player).Message((MessageType)2, "Stopped hacking", 0, (Sprite)null);
				}
				ModLog.Info("Stopped hacking '" + ((Object)((Component)_target).gameObject).name + "'.");
				_target = null;
				_collider = null;
				_localSurfacePoint = Vector3.zero;
				_player = null;
				_axe = null;
				_armedForCancel = false;
				_firstFailedAttempt = -1f;
			}
		}
	}
	internal static class InteractionInstaller
	{
		public static void Install(GameObject root)
		{
			CuttingInteraction cuttingInteraction = root.GetComponent<CuttingInteraction>();
			if ((Object)(object)cuttingInteraction == (Object)null)
			{
				cuttingInteraction = root.AddComponent<CuttingInteraction>();
			}
			int num = 0;
			Collider[] componentsInChildren = root.GetComponentsInChildren<Collider>(true);
			foreach (Collider val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null))
				{
					TreeInteractionPoint treeInteractionPoint = ((Component)val).GetComponent<TreeInteractionPoint>();
					if ((Object)(object)treeInteractionPoint == (Object)null)
					{
						treeInteractionPoint = ((Component)val).gameObject.AddComponent<TreeInteractionPoint>();
					}
					treeInteractionPoint.Target = cuttingInteraction;
					num++;
				}
			}
			if (num == 0)
			{
				ModLog.Warn("No collider interaction points found on '" + ((Object)root).name + "'.");
			}
		}
	}
	[HarmonyPatch(typeof(TreeBase), "Awake")]
	internal static class TreeBaseAwakePatch
	{
		private static void Postfix(TreeBase __instance)
		{
			if ((Object)(object)__instance.m_logPrefab != (Object)null)
			{
				InteractionInstaller.Install(((Component)__instance).gameObject);
			}
		}
	}
	[HarmonyPatch(typeof(TreeLog), "Awake")]
	internal static class TreeLogAwakePatch
	{
		private static void Postfix(TreeLog __instance)
		{
			InteractionInstaller.Install(((Component)__instance).gameObject);
			if ((Object)(object)((Component)__instance).GetComponent<LogDecay>() == (Object)null)
			{
				((Component)__instance).gameObject.AddComponent<LogDecay>();
			}
		}
	}
	public sealed class LogDecay : MonoBehaviour
	{
		private const string WorkedKey = "HackAndSap_LogLastWorked";

		private ZNetView _nview;

		private void Awake()
		{
			_nview = ((Component)this).GetComponent<ZNetView>();
		}

		private void Start()
		{
			EnsureTimestamp();
			((MonoBehaviour)this).InvokeRepeating("CheckDecay", Random.Range(15f, 45f), 60f);
		}

		private void EnsureTimestamp()
		{
			if (!((Object)(object)_nview == (Object)null) && _nview.IsValid() && _nview.IsOwner())
			{
				ZDO zDO = _nview.GetZDO();
				if (zDO != null && zDO.GetLong("HackAndSap_LogLastWorked", 0L) <= 0)
				{
					zDO.Set("HackAndSap_LogLastWorked", RenewableRegistry.NowTicks());
				}
			}
		}

		public void MarkWorked()
		{
			if (!((Object)(object)_nview == (Object)null) && _nview.IsValid() && _nview.IsOwner())
			{
				ZDO zDO = _nview.GetZDO();
				if (zDO != null)
				{
					zDO.Set("HackAndSap_LogLastWorked", RenewableRegistry.NowTicks());
				}
			}
		}

		private void CheckDecay()
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			if (!ModConfig.Enabled.Value || ModConfig.LogDecayDays.Value <= 0f || (Object)(object)_nview == (Object)null || !_nview.IsValid() || !_nview.IsOwner())
			{
				return;
			}
			EnsureTimestamp();
			ZDO zDO = _nview.GetZDO();
			if (zDO == null)
			{
				return;
			}
			if (RenewableRegistry.IsBaseProtected(((Component)this).transform.position))
			{
				zDO.Set("HackAndSap_LogLastWorked", RenewableRegistry.NowTicks());
				return;
			}
			long num = zDO.GetLong("HackAndSap_LogLastWorked", 0L);
			if (num > 0 && !(Math.Max(0.0, (double)(RenewableRegistry.NowTicks() - num) / 10000000.0) < (double)ModConfig.LogDecayDays.Value * RenewableRegistry.GetDayLengthSeconds()))
			{
				ModLog.Info("Decayed abandoned log '" + ((Object)((Component)this).gameObject).name + "' away.");
				ZNetScene.instance.Destroy(((Component)this).gameObject);
			}
		}
	}
	internal static class ModConfig
	{
		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<bool> EnableTreeInteraction;

		public static ConfigEntry<bool> EnableLogInteraction;

		public static ConfigEntry<float> MaximumDistance;

		public static ConfigEntry<float> StumpRenewalDays;

		public static ConfigEntry<float> LogDecayDays;

		public static ConfigEntry<bool> ProtectPlayerBases;

		public static ConfigEntry<bool> EnableLogs;

		public static void Bind(ConfigFile config)
		{
			Enabled = ConfigFileExtensions.BindConfig<bool>(config, "General", "Enabled", true, "Master switch for Hack 'n Sap gameplay.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			EnableLogs = ConfigFileExtensions.BindConfig<bool>(config, "General", "Enable Logs", false, "Write detailed discovery and lifecycle messages to the BepInEx log. Client-only.", false, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			EnableTreeInteraction = ConfigFileExtensions.BindConfig<bool>(config, "Interaction", "Standing Trees", true, "Show the Get Hacking interaction on every discovered tree that produces a felled log.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			EnableLogInteraction = ConfigFileExtensions.BindConfig<bool>(config, "Interaction", "Felled Logs", true, "Show the Get Hacking interaction on felled logs and split logs.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			MaximumDistance = ConfigFileExtensions.BindConfig<float>(config, "Interaction", "Maximum Distance", 3.2f, "Optional upper cap for interaction distance. Hack 'n Sap always requires the player to stand directly beside the tree or log.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			StumpRenewalDays = ConfigFileExtensions.BindConfig<float>(config, "Renewal", "Stump Renewal Days", 5f, "In-game days before an unprotected stump becomes a natural sapling. Zero disables renewal.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			LogDecayDays = ConfigFileExtensions.BindConfig<float>(config, "Renewal", "Log Decay Days", 3f, "In-game days since a log was spawned or last chopped before it decays away without drops. Zero disables decay.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
			ProtectPlayerBases = ConfigFileExtensions.BindConfig<bool>(config, "Renewal", "Protect Player Bases", true, "Prevent stump renewal and log decay inside a player-base effect area.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
		}
	}
	internal static class ModLog
	{
		private static ManualLogSource _log;

		public static void Init(ManualLogSource log)
		{
			_log = log;
		}

		public static void Info(string message)
		{
			if (ModConfig.EnableLogs != null && ModConfig.EnableLogs.Value)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)message);
				}
			}
		}

		public static void Warn(string message)
		{
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogWarning((object)message);
			}
		}

		public static void Error(string message)
		{
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogError((object)message);
			}
		}
	}
	[BepInPlugin("com.virtualbjorn.hackandsap", "HackAndSap", "0.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.virtualbjorn.hackandsap";

		public const string PluginName = "HackAndSap";

		public const string PluginVersion = "0.1.0";

		private Harmony _harmony;

		private void Awake()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			ModConfig.Bind(((BaseUnityPlugin)this).Config);
			ModLog.Init(((BaseUnityPlugin)this).Logger);
			_harmony = new Harmony("com.virtualbjorn.hackandsap");
			_harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Hack 'n Sap v0.1.0 loaded. Get Hacking.");
		}

		private void Update()
		{
			CuttingSession.Tick(Time.deltaTime);
		}

		private void OnDestroy()
		{
			CuttingSession.Cancel(notify: false);
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class RenewableRegistry
	{
		private static readonly Dictionary<string, GameObject> SaplingsByTree = new Dictionary<string, GameObject>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, HashSet<string>> TreesByStump = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);

		private static bool _initialized;

		public static void Initialize(ZNetScene scene)
		{
			if ((Object)(object)scene == (Object)null)
			{
				return;
			}
			SaplingsByTree.Clear();
			TreesByStump.Clear();
			List<GameObject> source = scene.m_prefabs.Where((GameObject p) => (Object)(object)p != (Object)null).ToList();
			List<TreeBase> list = (from p in source
				select p.GetComponent<TreeBase>() into t
				where (Object)(object)t != (Object)null && (Object)(object)t.m_logPrefab != (Object)null
				select t).ToList();
			List<Plant> source2 = (from p in source
				select p.GetComponent<Plant>() into p
				where (Object)(object)p != (Object)null
				select p).ToList();
			Plant val = source2.FirstOrDefault();
			foreach (TreeBase item in list)
			{
				string treeName = Normalize(((Object)((Component)item).gameObject).name);
				if ((Object)(object)item.m_stubPrefab != (Object)null)
				{
					RegisterStump(item.m_stubPrefab, treeName);
				}
				Plant val2 = ((IEnumerable<Plant>)source2).FirstOrDefault((Func<Plant, bool>)((Plant p) => p.m_grownPrefabs != null && p.m_grownPrefabs.Any((GameObject g) => (Object)(object)g != (Object)null && Normalize(((Object)g).name).Equals(treeName, StringComparison.OrdinalIgnoreCase))));
				GameObject val3 = CreateNaturalSapling(((Component)item).gameObject, val2 ?? val);
				if ((Object)(object)val3 != (Object)null)
				{
					SaplingsByTree[treeName] = val3;
				}
			}
			_initialized = true;
			ModLog.Info($"Discovered {list.Count} fellable tree prefabs and " + $"{SaplingsByTree.Count} renewable saplings.");
		}

		private static void RegisterStump(GameObject stump, string treeName)
		{
			string key = Normalize(((Object)stump).name);
			if (!TreesByStump.TryGetValue(key, out var value))
			{
				value = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				TreesByStump[key] = value;
			}
			value.Add(treeName);
			if ((Object)(object)stump.GetComponent<StumpRenewal>() == (Object)null)
			{
				stump.AddComponent<StumpRenewal>();
			}
		}

		private static GameObject CreateNaturalSapling(GameObject matureTree, Plant basePlant)
		{
			if ((Object)(object)matureTree == (Object)null || (Object)(object)basePlant == (Object)null)
			{
				return null;
			}
			string value = Normalize(((Object)matureTree).name);
			string text = "HackAndSap_Sapling_" + StableId(value);
			PrefabManager instance = PrefabManager.Instance;
			GameObject val = instance.GetPrefab(text);
			bool flag = (Object)(object)val == (Object)null;
			if (flag)
			{
				val = instance.CreateClonedPrefab(text, ((Component)basePlant).gameObject);
			}
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			((Object)val).name = text;
			Plant component = val.GetComponent<Plant>();
			if ((Object)(object)component == (Object)null)
			{
				return null;
			}
			component.m_name = "Young " + PrettyName(value);
			component.m_needCultivatedGround = false;
			component.m_destroyIfCantGrow = false;
			component.m_grownPrefabs = (GameObject[])(object)new GameObject[1] { matureTree };
			if (flag)
			{
				instance.AddPrefab(val);
			}
			instance.RegisterToZNetScene(val);
			return val;
		}

		public static GameObject GetSapling(string treePrefab)
		{
			if (!_initialized || string.IsNullOrEmpty(treePrefab))
			{
				return null;
			}
			SaplingsByTree.TryGetValue(Normalize(treePrefab), out var value);
			return value;
		}

		public static string InferUniqueTree(string stumpPrefab)
		{
			if (!_initialized || string.IsNullOrEmpty(stumpPrefab))
			{
				return null;
			}
			if (!TreesByStump.TryGetValue(Normalize(stumpPrefab), out var value))
			{
				return null;
			}
			if (value.Count != 1)
			{
				return null;
			}
			return value.First();
		}

		public static string GetPrefabName(ZNetView nview, GameObject gameObject)
		{
			if ((Object)(object)nview != (Object)null && nview.IsValid())
			{
				ZDO zDO = nview.GetZDO();
				GameObject val = ((zDO != null && (Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(zDO.GetPrefab()) : null);
				if ((Object)(object)val != (Object)null)
				{
					return Normalize(((Object)val).name);
				}
			}
			if (!((Object)(object)gameObject != (Object)null))
			{
				return null;
			}
			return Normalize(((Object)gameObject).name);
		}

		public static double GetDayLengthSeconds()
		{
			if (!((Object)(object)EnvMan.instance != (Object)null) || EnvMan.instance.m_dayLengthSec <= 0)
			{
				return 1800.0;
			}
			return EnvMan.instance.m_dayLengthSec;
		}

		public static long NowTicks()
		{
			if (!((Object)(object)ZNet.instance != (Object)null))
			{
				return DateTime.UtcNow.Ticks;
			}
			return ZNet.instance.GetTime().Ticks;
		}

		public static bool IsBaseProtected(Vector3 point)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			if (ModConfig.ProtectPlayerBases.Value)
			{
				return (Object)(object)EffectArea.IsPointInsideArea(point, (Type)4, 0f) != (Object)null;
			}
			return false;
		}

		public static string Normalize(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return string.Empty;
			}
			return value.Replace("(Clone)", string.Empty).Trim();
		}

		private static string PrettyName(string value)
		{
			return value.Replace('_', ' ');
		}

		private static string StableId(string value)
		{
			uint num = 2166136261u;
			foreach (char c in value)
			{
				num = (num ^ char.ToLowerInvariant(c)) * 16777619;
			}
			return num.ToString("X8");
		}
	}
	[HarmonyPatch(typeof(ZNetScene), "Awake")]
	[HarmonyPriority(0)]
	[HarmonyAfter(new string[] { "com.jotunn.jotunn" })]
	internal static class ZNetSceneAwakePatch
	{
		private static void Postfix(ZNetScene __instance)
		{
			RenewableRegistry.Initialize(__instance);
		}
	}
	[HarmonyPatch(typeof(TreeBase), "SpawnLog")]
	internal static class TreeBaseSpawnLogPatch
	{
		private static void Prefix(TreeBase __instance)
		{
			SpawnContext.Enter(__instance);
		}

		private static Exception Finalizer(Exception __exception)
		{
			SpawnContext.Exit();
			return __exception;
		}
	}
	[HarmonyPatch(typeof(TreeLog), "RPC_Damage")]
	internal static class TreeLogDamagePatch
	{
		private static void Prefix(TreeLog __instance, out float __state)
		{
			__state = float.NaN;
			ZNetView val = (((Object)(object)__instance != (Object)null) ? ((Component)__instance).GetComponent<ZNetView>() : null);
			if (!((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner())
			{
				__state = val.GetZDO().GetFloat(ZDOVars.s_health, __instance.m_health);
			}
		}

		private static void Postfix(TreeLog __instance, HitData hit, float __state)
		{
			if (!float.IsNaN(__state) && hit != null && !(hit.m_damage.m_chop <= 0f))
			{
				ZNetView val = (((Object)(object)__instance != (Object)null) ? ((Component)__instance).GetComponent<ZNetView>() : null);
				if (!((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner() && val.GetZDO().GetFloat(ZDOVars.s_health, __instance.m_health) < __state)
				{
					((Component)__instance).GetComponent<LogDecay>()?.MarkWorked();
				}
			}
		}
	}
	internal static class SpawnContext
	{
		public static string CurrentTreePrefab { get; private set; }

		public static void Enter(TreeBase tree)
		{
			CurrentTreePrefab = RenewableRegistry.GetPrefabName(((Object)(object)tree != (Object)null) ? ((Component)tree).GetComponent<ZNetView>() : null, (tree != null) ? ((Component)tree).gameObject : null);
		}

		public static void Exit()
		{
			CurrentTreePrefab = null;
		}
	}
	public sealed class StumpRenewal : MonoBehaviour
	{
		private const string SourceKey = "HackAndSap_SourceTree";

		private const string BornKey = "HackAndSap_StumpBorn";

		private ZNetView _nview;

		private string _spawnSource;

		private void Awake()
		{
			_nview = ((Component)this).GetComponent<ZNetView>();
			_spawnSource = SpawnContext.CurrentTreePrefab;
		}

		private void Start()
		{
			EnsureState();
			((MonoBehaviour)this).InvokeRepeating("CheckRenewal", Random.Range(15f, 45f), 60f);
		}

		private void EnsureState()
		{
			if ((Object)(object)_nview == (Object)null || !_nview.IsValid() || !_nview.IsOwner())
			{
				return;
			}
			ZDO zDO = _nview.GetZDO();
			if (zDO == null)
			{
				return;
			}
			if (string.IsNullOrEmpty(zDO.GetString("HackAndSap_SourceTree", string.Empty)))
			{
				string text = _spawnSource;
				if (string.IsNullOrEmpty(text))
				{
					text = RenewableRegistry.InferUniqueTree(RenewableRegistry.GetPrefabName(_nview, ((Component)this).gameObject));
				}
				if (!string.IsNullOrEmpty(text))
				{
					zDO.Set("HackAndSap_SourceTree", text);
				}
			}
			if (zDO.GetLong("HackAndSap_StumpBorn", 0L) <= 0)
			{
				zDO.Set("HackAndSap_StumpBorn", RenewableRegistry.NowTicks());
			}
		}

		private void CheckRenewal()
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			if (!ModConfig.Enabled.Value || ModConfig.StumpRenewalDays.Value <= 0f || (Object)(object)_nview == (Object)null || !_nview.IsValid() || !_nview.IsOwner())
			{
				return;
			}
			EnsureState();
			ZDO zDO = _nview.GetZDO();
			if (zDO == null)
			{
				return;
			}
			if (RenewableRegistry.IsBaseProtected(((Component)this).transform.position))
			{
				zDO.Set("HackAndSap_StumpBorn", RenewableRegistry.NowTicks());
				return;
			}
			long num = zDO.GetLong("HackAndSap_StumpBorn", 0L);
			if (num <= 0 || Math.Max(0.0, (double)(RenewableRegistry.NowTicks() - num) / 10000000.0) < (double)ModConfig.StumpRenewalDays.Value * RenewableRegistry.GetDayLengthSeconds())
			{
				return;
			}
			string text = zDO.GetString("HackAndSap_SourceTree", string.Empty);
			GameObject sapling = RenewableRegistry.GetSapling(text);
			if ((Object)(object)sapling == (Object)null)
			{
				ModLog.Warn("No renewal sapling registered for stump source '" + text + "'.");
				return;
			}
			Quaternion val = Quaternion.Euler(0f, ((Component)this).transform.eulerAngles.y, 0f);
			GameObject val2 = Object.Instantiate<GameObject>(sapling, ((Component)this).transform.position, val);
			ZNetView val3 = (((Object)(object)val2 != (Object)null) ? val2.GetComponent<ZNetView>() : null);
			if ((Object)(object)val3 != (Object)null)
			{
				val3.SetLocalScale(((Component)this).transform.localScale);
			}
			ModLog.Info("Renewed stump '" + ((Object)((Component)this).gameObject).name + "' into sapling for '" + text + "'.");
			ZNetScene.instance.Destroy(((Component)this).gameObject);
		}
	}
	public sealed class TreeInteractionPoint : MonoBehaviour, Hoverable, Interactable
	{
		public CuttingInteraction Target;

		private Collider _collider;

		private Collider PointCollider
		{
			get
			{
				if (!((Object)(object)_collider != (Object)null))
				{
					return _collider = ((Component)this).GetComponent<Collider>();
				}
				return _collider;
			}
		}

		private void Awake()
		{
			_collider = ((Component)this).GetComponent<Collider>();
		}

		public string GetHoverName()
		{
			if (!((Object)(object)Target != (Object)null))
			{
				return string.Empty;
			}
			return Target.GetHoverName(PointCollider);
		}

		public string GetHoverText()
		{
			if (!((Object)(object)Target != (Object)null))
			{
				return string.Empty;
			}
			return Target.GetHoverText(PointCollider);
		}

		public bool Interact(Humanoid user, bool hold, bool alt)
		{
			if ((Object)(object)Target != (Object)null)
			{
				return Target.Interact(user, hold, alt, PointCollider);
			}
			return false;
		}

		public bool UseItem(Humanoid user, ItemData item)
		{
			return false;
		}
	}
}