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(); _log = ((Component)this).GetComponent(); _nview = ((Component)this).GetComponent(); } 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("[$KEY_Use] " + 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(); if ((Object)(object)cuttingInteraction == (Object)null) { cuttingInteraction = root.AddComponent(); } int num = 0; Collider[] componentsInChildren = root.GetComponentsInChildren(true); foreach (Collider val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { TreeInteractionPoint treeInteractionPoint = ((Component)val).GetComponent(); if ((Object)(object)treeInteractionPoint == (Object)null) { treeInteractionPoint = ((Component)val).gameObject.AddComponent(); } 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() == (Object)null) { ((Component)__instance).gameObject.AddComponent(); } } } public sealed class LogDecay : MonoBehaviour { private const string WorkedKey = "HackAndSap_LogLastWorked"; private ZNetView _nview; private void Awake() { _nview = ((Component)this).GetComponent(); } 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 Enabled; public static ConfigEntry EnableTreeInteraction; public static ConfigEntry EnableLogInteraction; public static ConfigEntry MaximumDistance; public static ConfigEntry StumpRenewalDays; public static ConfigEntry LogDecayDays; public static ConfigEntry ProtectPlayerBases; public static ConfigEntry EnableLogs; public static void Bind(ConfigFile config) { Enabled = ConfigFileExtensions.BindConfig(config, "General", "Enabled", true, "Master switch for Hack 'n Sap gameplay.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); EnableLogs = ConfigFileExtensions.BindConfig(config, "General", "Enable Logs", false, "Write detailed discovery and lifecycle messages to the BepInEx log. Client-only.", false, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); EnableTreeInteraction = ConfigFileExtensions.BindConfig(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)null, (ConfigurationManagerAttributes)null); EnableLogInteraction = ConfigFileExtensions.BindConfig(config, "Interaction", "Felled Logs", true, "Show the Get Hacking interaction on felled logs and split logs.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); MaximumDistance = ConfigFileExtensions.BindConfig(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)null, (ConfigurationManagerAttributes)null); StumpRenewalDays = ConfigFileExtensions.BindConfig(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)null, (ConfigurationManagerAttributes)null); LogDecayDays = ConfigFileExtensions.BindConfig(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)null, (ConfigurationManagerAttributes)null); ProtectPlayerBases = ConfigFileExtensions.BindConfig(config, "Renewal", "Protect Player Bases", true, "Prevent stump renewal and log decay inside a player-base effect area.", true, (int?)null, (AcceptableValueBase)null, (Action)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 SaplingsByTree = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary> TreesByStump = new Dictionary>(StringComparer.OrdinalIgnoreCase); private static bool _initialized; public static void Initialize(ZNetScene scene) { if ((Object)(object)scene == (Object)null) { return; } SaplingsByTree.Clear(); TreesByStump.Clear(); List source = scene.m_prefabs.Where((GameObject p) => (Object)(object)p != (Object)null).ToList(); List list = (from p in source select p.GetComponent() into t where (Object)(object)t != (Object)null && (Object)(object)t.m_logPrefab != (Object)null select t).ToList(); List source2 = (from p in source select p.GetComponent() 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)source2).FirstOrDefault((Func)((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(StringComparer.OrdinalIgnoreCase); TreesByStump[key] = value; } value.Add(treeName); if ((Object)(object)stump.GetComponent() == (Object)null) { stump.AddComponent(); } } 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(); 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() : 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() : null); if (!((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner() && val.GetZDO().GetFloat(ZDOVars.s_health, __instance.m_health) < __state) { ((Component)__instance).GetComponent()?.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() : 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(); _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(sapling, ((Component)this).transform.position, val); ZNetView val3 = (((Object)(object)val2 != (Object)null) ? val2.GetComponent() : 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(); } return _collider; } } private void Awake() { _collider = ((Component)this).GetComponent(); } 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; } } }