using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("modbykou_minechop")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("KLX for King drip woo")] [assembly: AssemblyDescription("Configurable hits-to-kill and stamina-per-hit for mining and chopping. Server-authoritative.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("ConfigurableMineChop")] [assembly: AssemblyTitle("modbykou_minechop")] [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 ConfigurableMineChop { [BepInPlugin("kou_minechop", "modbykou_minechop", "1.5.0")] public class Plugin : BaseUnityPlugin { internal class SyncedValues { public bool Enabled = true; public int TreeMinHits = 2; public int TreeMaxHits = 2; public int RockMinHits = 3; public int RockMaxHits = 7; public float ChopStamina = 15f; public float PickaxeStamina = 15f; public string RockStaminaTable = "3:50,4:40,5:30,6:15,7:10"; public string ActivationKey = "LeftAlt"; } [HarmonyPatch(typeof(ZNet), "Awake")] private static class ZNet_Awake_Patch { [HarmonyPostfix] private static void Postfix() { s_receivedFromServer = false; s_rpcRegistered = false; RefreshEffectiveFromLocal(); RegisterSyncRpcOnce(); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)string.Format("[{0}] ZNet.Awake — Effective: Trees={1}-{2}@{3}, Rocks={4}-{5}@{6}, Enabled={7}", "modbykou_minechop", Effective.TreeMinHits, Effective.TreeMaxHits, Effective.ChopStamina, Effective.RockMinHits, Effective.RockMaxHits, Effective.PickaxeStamina, Effective.Enabled)); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private static class ZNet_RPC_PeerInfo_Patch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZRpc rpc) { if (!IsServer() || rpc == null || (Object)(object)__instance == (Object)null || !cfgServerAuthoritative.Value) { return; } ZNetPeer val = null; foreach (ZNetPeer peer in __instance.GetPeers()) { if (peer != null && peer.m_rpc == rpc) { val = peer; break; } } if (val != null && val.m_uid != 0L) { RefreshEffectiveFromLocal(); PushConfigToPeer(val.m_uid); } } } [HarmonyPatch(typeof(TreeBase), "Damage")] private static class TreeBase_Damage_Patch { [HarmonyPrefix] private static void Prefix(TreeBase __instance, ref HitData hit) { if (Effective.Enabled && ActivationKeyHeld() && !((Object)(object)__instance == (Object)null) && hit != null && !(hit.m_damage.m_chop <= 0f)) { ZNetView component = ((Component)__instance).GetComponent(); ZDO zdo = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); int num = RollHitsForTarget(zdo, Effective.TreeMinHits, Effective.TreeMaxHits); int soFar; bool flag = CountSwing(zdo, num, out soFar); hit.m_damage.m_chop = (flag ? 9999f : 1f); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)string.Format("[{0}] TreeBase '{1}' swing {2}/{3} final={4}", "modbykou_minechop", ((Object)((Component)__instance).gameObject).name, soFar, num, flag)); } } } } [HarmonyPatch(typeof(TreeLog), "Damage")] private static class TreeLog_Damage_Patch { [HarmonyPrefix] private static void Prefix(TreeLog __instance, ref HitData hit) { if (Effective.Enabled && ActivationKeyHeld() && !((Object)(object)__instance == (Object)null) && hit != null && !(hit.m_damage.m_chop <= 0f)) { ZNetView component = ((Component)__instance).GetComponent(); ZDO zdo = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); int num = RollHitsForTarget(zdo, Effective.TreeMinHits, Effective.TreeMaxHits); int soFar; bool flag = CountSwing(zdo, num, out soFar); hit.m_damage.m_chop = (flag ? 9999f : 1f); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)string.Format("[{0}] TreeLog '{1}' swing {2}/{3} final={4}", "modbykou_minechop", ((Object)((Component)__instance).gameObject).name, soFar, num, flag)); } } } } [HarmonyPatch(typeof(MineRock), "Damage")] private static class MineRock_Damage_Patch { [HarmonyPrefix] private static void Prefix(MineRock __instance, ref HitData hit) { if (Effective.Enabled && ActivationKeyHeld() && !((Object)(object)__instance == (Object)null) && hit != null && !(hit.m_damage.m_pickaxe <= 0f)) { ZNetView component = ((Component)__instance).GetComponent(); ZDO zdo = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); int num = (s_lastRockHits = RollHitsForTarget(zdo, Effective.RockMinHits, Effective.RockMaxHits)); int soFar; bool flag = CountSwing(zdo, num, out soFar); hit.m_damage.m_pickaxe = (flag ? 9999f : 1f); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)string.Format("[{0}] MineRock '{1}' swing {2}/{3} final={4}", "modbykou_minechop", ((Object)((Component)__instance).gameObject).name, soFar, num, flag)); } } } } [HarmonyPatch(typeof(MineRock5), "Damage")] private static class MineRock5_Damage_Patch { private static readonly MethodInfo DamageAreaMethod = AccessTools.Method(typeof(MineRock5), "DamageArea", (Type[])null, (Type[])null); private static readonly FieldInfo HitAreasField = AccessTools.Field(typeof(MineRock5), "m_hitAreas"); private static readonly FieldInfo NViewField = AccessTools.Field(typeof(MineRock5), "m_nview"); [HarmonyPrefix] private static bool Prefix(MineRock5 __instance, ref HitData hit) { if (!Effective.Enabled || !ActivationKeyHeld() || (Object)(object)__instance == (Object)null || hit == null) { return true; } if (hit.m_damage.m_pickaxe <= 0f) { return true; } if (DamageAreaMethod == null || HitAreasField == null || NViewField == null) { return true; } object? value = NViewField.GetValue(__instance); ZNetView val = (ZNetView)((value is ZNetView) ? value : null); if ((Object)(object)val == (Object)null || !val.IsValid()) { return true; } ZDO zDO = val.GetZDO(); if (zDO == null) { return true; } if (!(HitAreasField.GetValue(__instance) is IList list)) { return true; } int num = (s_lastRockHits = RollHitsForTarget(zDO, Effective.RockMinHits, Effective.RockMaxHits)); if (!CountSwing(zDO, num, out var soFar)) { hit.m_damage.m_pickaxe = 1f; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)string.Format("[{0}] MineRock5 '{1}' swing {2}/{3}", "modbykou_minechop", ((Object)((Component)__instance).gameObject).name, soFar, num)); } return true; } hit.m_damage.m_pickaxe = 9999f; int num2 = 0; for (int i = 0; i < list.Count; i++) { if (!val.IsValid()) { break; } if (val.GetZDO() == null) { break; } try { DamageAreaMethod.Invoke(__instance, new object[2] { i, hit }); num2++; } catch (Exception ex) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)string.Format("[{0}] DamageArea({1}) failed: {2}", "modbykou_minechop", i, ex.Message)); } } } ManualLogSource log3 = Log; if (log3 != null) { log3.LogInfo((object)string.Format("[{0}] MineRock5 '{1}' FINAL swing {2}/{3}, areasHit={4}", "modbykou_minechop", ((Object)((Component)__instance).gameObject).name, soFar, num, num2)); } return false; } } [HarmonyPatch(typeof(Destructible), "Damage")] private static class Destructible_Damage_Patch { [HarmonyPrefix] private static void Prefix(Destructible __instance, ref HitData hit) { //IL_0022: 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) //IL_0029: Invalid comparison between Unknown and I4 //IL_002d: Invalid comparison between Unknown and I4 if (!Effective.Enabled || !ActivationKeyHeld() || (Object)(object)__instance == (Object)null || hit == null) { return; } DestructibleType destructibleType = __instance.GetDestructibleType(); bool flag = (int)destructibleType == 2; bool flag2 = (int)destructibleType == 1; if (!flag && !flag2) { return; } bool flag3 = flag && hit.m_damage.m_chop > 0f; bool flag4 = flag2 && hit.m_damage.m_pickaxe > 0f; if (flag3 || flag4) { ZNetView component = ((Component)__instance).GetComponent(); ZDO zdo = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); int min = (flag ? Effective.TreeMinHits : Effective.RockMinHits); int max = (flag ? Effective.TreeMaxHits : Effective.RockMaxHits); int num = RollHitsForTarget(zdo, min, max); if (!flag3) { s_lastRockHits = num; } int soFar; bool flag5 = CountSwing(zdo, num, out soFar); if (flag3) { hit.m_damage.m_chop = (flag5 ? 9999f : 1f); } else { hit.m_damage.m_pickaxe = (flag5 ? 9999f : 1f); } ManualLogSource log = Log; if (log != null) { log.LogInfo((object)string.Format("[{0}] Destructible({1}) '{2}' swing {3}/{4} final={5}", "modbykou_minechop", flag3 ? "tree" : "rock", ((Object)((Component)__instance).gameObject).name, soFar, num, flag5)); } } } } [HarmonyPatch(typeof(Attack), "GetAttackStamina")] private static class Attack_GetAttackStamina_Patch { [HarmonyPostfix] private static void Postfix(Attack __instance, ref float __result) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected I4, but got Unknown if (!Effective.Enabled || !ActivationKeyHeld() || __instance == null) { return; } ItemData weapon = __instance.GetWeapon(); if (weapon == null || weapon.m_shared == null) { return; } switch ((int)weapon.m_shared.m_skillType) { case 7: __result = Effective.ChopStamina; break; case 12: { Player localPlayer = Player.m_localPlayer; float num = ((s_lastRockHits > 0) ? RockStaminaPercent(s_lastRockHits) : (-1f)); if ((Object)(object)localPlayer != (Object)null && num >= 0f) { __result = ((Character)localPlayer).GetMaxStamina() * num; } else { __result = Effective.PickaxeStamina; } break; } } } } public const string PluginGuid = "kou_minechop"; public const string PluginName = "modbykou_minechop"; public const string PluginVersion = "1.5.0"; private const string SyncRpcName = "CfgMineChop_Sync"; internal static ManualLogSource Log; internal static Harmony HarmonyInstance; internal static ConfigEntry cfgEnabled; internal static ConfigEntry cfgServerAuthoritative; internal static ConfigEntry cfgTreeMinHits; internal static ConfigEntry cfgTreeMaxHits; internal static ConfigEntry cfgRockMinHits; internal static ConfigEntry cfgRockMaxHits; internal static ConfigEntry cfgChopStamina; internal static ConfigEntry cfgPickaxeStamina; internal static ConfigEntry cfgRockStaminaTable; internal static ConfigEntry cfgActivationKey; internal static SyncedValues Effective = new SyncedValues(); private static bool s_receivedFromServer = false; private static bool s_rpcRegistered = false; private static KeyCode s_activationKeyCode = (KeyCode)308; private static string s_activationKeyRaw; private static readonly Dictionary s_hitCounts = new Dictionary(); internal static int s_lastRockHits = 0; private const int SKILL_AXES = 7; private const int SKILL_PICKAXES = 12; internal static bool ActivationKeyHeld() { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006f: 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) string activationKey = Effective.ActivationKey; if (!string.Equals(activationKey, s_activationKeyRaw, StringComparison.Ordinal)) { s_activationKeyRaw = activationKey; if (string.IsNullOrEmpty(activationKey) || !Enum.TryParse(activationKey.Trim(), ignoreCase: true, out s_activationKeyCode)) { s_activationKeyCode = (KeyCode)0; if (!string.IsNullOrEmpty(activationKey)) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[modbykou_minechop] ActivationKey '" + activationKey + "' is not a valid UnityEngine.KeyCode — mod stays always active.")); } } } } if ((int)s_activationKeyCode != 0) { return Input.GetKey(s_activationKeyCode); } return true; } private void Awake() { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Expected O, but got Unknown //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Expected O, but got Unknown //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Expected O, but got Unknown //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Expected O, but got Unknown //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; cfgEnabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Master switch. If false, mod behaves as if uninstalled."); cfgServerAuthoritative = ((BaseUnityPlugin)this).Config.Bind("General", "ServerAuthoritative", true, "When true, a joined client's values are overridden by the server's config on connect. Set both server and client to true (default)."); cfgTreeMinHits = ((BaseUnityPlugin)this).Config.Bind("Trees", "MinHits", 2, new ConfigDescription("Minimum hits to fell a tree/log. Set MinHits == MaxHits for fixed hit count.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 500), Array.Empty())); cfgTreeMaxHits = ((BaseUnityPlugin)this).Config.Bind("Trees", "MaxHits", 2, new ConfigDescription("Maximum hits to fell a tree/log. If greater than MinHits, per-target hits are rolled uniformly (deterministic per target).", (AcceptableValueBase)(object)new AcceptableValueRange(1, 500), Array.Empty())); cfgChopStamina = ((BaseUnityPlugin)this).Config.Bind("Trees", "StaminaPerHit", 15f, new ConfigDescription("Stamina drained per axe swing (regardless of skill/perks).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 500f), Array.Empty())); cfgRockMinHits = ((BaseUnityPlugin)this).Config.Bind("Rocks", "MinHits", 3, new ConfigDescription("Minimum hits to destroy an ore vein/rock/mine-area. Set MinHits == MaxHits for fixed hit count.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 500), Array.Empty())); cfgRockMaxHits = ((BaseUnityPlugin)this).Config.Bind("Rocks", "MaxHits", 7, new ConfigDescription("Maximum hits to destroy an ore vein/rock/mine-area. If greater than MinHits, per-target hits are rolled uniformly.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 500), Array.Empty())); cfgPickaxeStamina = ((BaseUnityPlugin)this).Config.Bind("Rocks", "StaminaPerHit", 15f, new ConfigDescription("Fallback stamina per pickaxe swing when no entry matches in StaminaPercentTable.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 500f), Array.Empty())); cfgRockStaminaTable = ((BaseUnityPlugin)this).Config.Bind("Rocks", "StaminaPercentTable", "3:50,4:40,5:30,6:15,7:10", "Per-swing mining stamina as PERCENT of max stamina, keyed by the rock's rolled hit count. Format: hits:percent,hits:percent,..."); cfgActivationKey = ((BaseUnityPlugin)this).Config.Bind("General", "ActivationKey", "LeftAlt", "The mod only takes effect while this key is HELD. Any UnityEngine.KeyCode name (e.g. LeftAlt, LeftControl, X). Empty or None = always active. Server-synced when ServerAuthoritative is true."); RefreshEffectiveFromLocal(); HarmonyInstance = new Harmony("kou_minechop"); HarmonyInstance.PatchAll(typeof(Plugin).Assembly); Log.LogInfo((object)"[modbykou_minechop] v1.5.0 loaded."); Log.LogInfo((object)(string.Format("[{0}] Effective config: Enabled={1} ", "modbykou_minechop", Effective.Enabled) + $"Trees={Effective.TreeMinHits}-{Effective.TreeMaxHits} hits @ {Effective.ChopStamina} stam, " + $"Rocks={Effective.RockMinHits}-{Effective.RockMaxHits} hits @ {Effective.PickaxeStamina} stam.")); } private void OnDestroy() { Harmony harmonyInstance = HarmonyInstance; if (harmonyInstance != null) { harmonyInstance.UnpatchSelf(); } } internal static void RefreshEffectiveFromLocal() { Effective.Enabled = cfgEnabled.Value; Effective.TreeMinHits = Math.Max(1, Math.Min(cfgTreeMinHits.Value, cfgTreeMaxHits.Value)); Effective.TreeMaxHits = Math.Max(Effective.TreeMinHits, cfgTreeMaxHits.Value); Effective.RockMinHits = Math.Max(1, Math.Min(cfgRockMinHits.Value, cfgRockMaxHits.Value)); Effective.RockMaxHits = Math.Max(Effective.RockMinHits, cfgRockMaxHits.Value); Effective.ChopStamina = Math.Max(0f, cfgChopStamina.Value); Effective.PickaxeStamina = Math.Max(0f, cfgPickaxeStamina.Value); Effective.RockStaminaTable = cfgRockStaminaTable.Value; Effective.ActivationKey = cfgActivationKey.Value; } internal static int RollHitsForTarget(ZDO zdo, int min, int max) { if (min >= max) { return min; } long num = ((zdo != null) ? ((object)zdo).GetHashCode() : 0); long num2 = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetWorldUID() : 0); return new Random((int)(num ^ (num2 >> 3) ^ (num2 << 17))).Next(min, max + 1); } internal static float ModifierFactor(DamageModifier m) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected I4, but got Unknown return (m - 1) switch { 0 => 0.5f, 4 => 0.25f, 6 => 0.75f, 1 => 1.5f, 5 => 2f, 7 => 1.25f, 2 => 0f, 3 => 0f, _ => 1f, }; } internal static float ComputePerHit(float maxHealth, int hits, DamageModifier mod) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) float num = ModifierFactor(mod); if (num <= 0.0001f) { return 9999f; } return (maxHealth / (float)hits + 0.5f) / num; } internal static float RockStaminaPercent(int hits) { string rockStaminaTable = Effective.RockStaminaTable; if (string.IsNullOrEmpty(rockStaminaTable)) { return -1f; } string[] array = rockStaminaTable.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split(new char[1] { ':' }); if (array2.Length == 2 && int.TryParse(array2[0].Trim(), out var result) && result == hits && float.TryParse(array2[1].Trim(), out var result2)) { return Mathf.Clamp(result2, 0f, 100f) / 100f; } } return -1f; } internal static bool CountSwing(ZDO zdo, int hitsNeeded, out int soFar) { int key = ((object)zdo)?.GetHashCode() ?? 0; s_hitCounts.TryGetValue(key, out soFar); soFar++; if (soFar >= hitsNeeded) { s_hitCounts.Remove(key); return true; } s_hitCounts[key] = soFar; return false; } internal static void RegisterSyncRpcOnce() { if (!s_rpcRegistered && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register("CfgMineChop_Sync", (Action)OnSyncReceived); s_rpcRegistered = true; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"[modbykou_minechop] Registered ZRoutedRpc handler 'CfgMineChop_Sync'."); } } } private static void OnSyncReceived(long sender, ZPackage pkg) { if (pkg == null) { return; } try { SyncedValues syncedValues = new SyncedValues { Enabled = pkg.ReadBool(), TreeMinHits = pkg.ReadInt(), TreeMaxHits = pkg.ReadInt(), RockMinHits = pkg.ReadInt(), RockMaxHits = pkg.ReadInt(), ChopStamina = pkg.ReadSingle(), PickaxeStamina = pkg.ReadSingle() }; try { syncedValues.RockStaminaTable = pkg.ReadString(); } catch { syncedValues.RockStaminaTable = Effective.RockStaminaTable; } try { syncedValues.ActivationKey = pkg.ReadString(); } catch { syncedValues.ActivationKey = Effective.ActivationKey; } Effective = syncedValues; s_receivedFromServer = true; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)string.Format("[{0}] Server sync applied: trees {1}-{2} @ {3} stam; rocks {4}-{5} @ {6} stam.", "modbykou_minechop", syncedValues.TreeMinHits, syncedValues.TreeMaxHits, syncedValues.ChopStamina, syncedValues.RockMinHits, syncedValues.RockMaxHits, syncedValues.PickaxeStamina)); } } catch (Exception ex) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("[modbykou_minechop] Failed to parse sync package: " + ex.Message)); } } } internal static void PushConfigToPeer(long peerUid) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown if (ZRoutedRpc.instance != null) { ZPackage val = new ZPackage(); val.Write(Effective.Enabled); val.Write(Effective.TreeMinHits); val.Write(Effective.TreeMaxHits); val.Write(Effective.RockMinHits); val.Write(Effective.RockMaxHits); val.Write(Effective.ChopStamina); val.Write(Effective.PickaxeStamina); val.Write(Effective.RockStaminaTable); val.Write(Effective.ActivationKey); ZRoutedRpc.instance.InvokeRoutedRPC(peerUid, "CfgMineChop_Sync", new object[1] { val }); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)string.Format("[{0}] Pushed config to peer {1}.", "modbykou_minechop", peerUid)); } } } internal static bool IsServer() { ZNet instance = ZNet.instance; if ((Object)(object)instance != (Object)null) { return instance.IsServer(); } return false; } } }