using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyCompany("LKKBalanceTweaks")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("BalanceTweaks")] [assembly: AssemblyFileVersion("1.0.3.0")] [assembly: AssemblyInformationalVersion("1.0.3")] [assembly: AssemblyProduct("LKKBalanceTweaks")] [assembly: AssemblyTitle("LKKBalanceTweaks")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("1.0.3.0")] namespace BalanceTweaksPlugin { [BepInPlugin("LKKBalanceTweaks", "LKKBalanceTweaks", "1.0.3")] public class BalanceTweaksPlugin : BaseUnityPlugin { public static ConfigEntry CreateNetworkPrefab; public static ConfigEntry ShotgunConductive; public static ConfigEntry ShotgunEnemyDamage; public static ConfigEntry BeltBagShotgunAmmo; public static ConfigEntry ShotgunWeight; public static ConfigEntry ShovelWeight; public static ConfigEntry KnifeWeight; public static ConfigEntry EnableShovelNarrowHitbox; public static ConfigEntry EnableShovelTriggerHit; public static ConfigEntry EnableShovelLinecastBlock; public static ConfigEntry EnableKnifeTriggerHit; public static ConfigEntry EnableKnifeLinecastBlock; public static ConfigEntry EnableJesterOwnershipFallback; public const string PLUGIN_GUID = "LKKBalanceTweaks"; public const string PLUGIN_NAME = "LKKBalanceTweaks"; public const string PLUGIN_VERSION = "1.0.3"; public const string PLUGIN_VERSION_FULL = "1.0.3.0"; private readonly Harmony harmony = new Harmony("LKKBalanceTweaks"); public static ManualLogSource logger; public static BalanceTweaksPlugin Instance; private void Awake() { Instance = this; logger = ((BaseUnityPlugin)this).Logger; CreateNetworkPrefab = ((BaseUnityPlugin)this).Config.Bind("General", "CreateNetworkPrefab", false, "Define whether to create a network prefab"); ShotgunConductive = ((BaseUnityPlugin)this).Config.Bind("General", "ShotgunConductive", true, "Define whether the shotgun is conductive"); ShotgunEnemyDamage = ((BaseUnityPlugin)this).Config.Bind("General", "ShotgunEnemyDamage", true, "Define whether to nerf shotgun damage"); BeltBagShotgunAmmo = ((BaseUnityPlugin)this).Config.Bind("General", "BeltBagShotgunAmmo", true, "Define whether the belt bag can't store shotgun ammo"); ShotgunWeight = ((BaseUnityPlugin)this).Config.Bind("Balance", "ShotgunWeight", true, "Define whether to modify shotgun weight"); ShovelWeight = ((BaseUnityPlugin)this).Config.Bind("Balance", "ShovelWeight", true, "Define whether to modify shovel weight"); KnifeWeight = ((BaseUnityPlugin)this).Config.Bind("Balance", "KnifeWeight", true, "Define whether to modify knife weight"); EnableShovelNarrowHitbox = ((BaseUnityPlugin)this).Config.Bind("HitDetection", "EnableShovelNarrowHitbox", true, "Define whether to narrow the shovel hit SphereCast radius"); EnableShovelTriggerHit = ((BaseUnityPlugin)this).Config.Bind("HitDetection", "EnableShovelTriggerHit", true, "Define whether to allow shovel to hit through trigger colliders"); EnableShovelLinecastBlock = ((BaseUnityPlugin)this).Config.Bind("HitDetection", "EnableShovelLinecastBlock", true, "Define whether to change shovel linecast queryTriggerInteraction"); EnableKnifeTriggerHit = ((BaseUnityPlugin)this).Config.Bind("HitDetection", "EnableKnifeTriggerHit", true, "Define whether to allow knife to hit through trigger colliders"); EnableKnifeLinecastBlock = ((BaseUnityPlugin)this).Config.Bind("HitDetection", "EnableKnifeLinecastBlock", true, "Define whether to change knife linecast queryTriggerInteraction"); EnableJesterOwnershipFallback = ((BaseUnityPlugin)this).Config.Bind("Jester", "EnableJesterOwnershipFallback", false, "Define whether to enable Jester fallback kill path"); harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"LKKBalanceTweaks is loaded!"); } } } namespace BalanceTweaksPlugin.Patches { [HarmonyPatch(typeof(BeltBagItem), "TryAddObjectToBagServerRpc")] internal static class BeltBagShotgunAmmoPatch { [HarmonyPrefix] private static bool Prefix(BeltBagItem __instance, NetworkObjectReference netObjectRef, int playerWhoAdded) { if (!((NetworkBehaviour)__instance).IsHost) { return true; } if (!BalanceTweaksPlugin.BeltBagShotgunAmmo.Value) { return true; } NetworkObject val = default(NetworkObject); if (((NetworkObjectReference)(ref netObjectRef)).TryGet(ref val, (NetworkManager)null) && ((Component)val).GetComponent() is GunAmmo) { __instance.CancelAddObjectToBagClientRpc(playerWhoAdded); return false; } return true; } } [HarmonyPatch(typeof(FlashlightItem), "DiscardItem")] internal static class FlashlightDiscardItemPatch { [HarmonyPrefix] private static void Prefix(FlashlightItem __instance) { if (((GrabbableObject)__instance).isBeingUsed && ((GrabbableObject)__instance).insertedBattery != null && !((GrabbableObject)__instance).insertedBattery.empty && (int)(((GrabbableObject)__instance).insertedBattery.charge * 100f) == 0) { ((GrabbableObject)__instance).UseUpBatteries(); } } } [HarmonyPatch(typeof(JesterAI), "OnCollideWithPlayer")] internal static class JesterOwnershipFallbackPatch { private static readonly FieldRef inKillAnimation = AccessTools.FieldRefAccess("inKillAnimation"); private static readonly MethodInfo isSeparatedByMineshaftElevator = AccessTools.Method(typeof(EnemyAI), "IsSeparatedByMineshaftElevator", (Type[])null, (Type[])null); [HarmonyPostfix] private static void Postfix(JesterAI __instance, Collider other) { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) if (BalanceTweaksPlugin.CreateNetworkPrefab.Value && BalanceTweaksPlugin.EnableJesterOwnershipFallback.Value && ((EnemyAI)__instance).currentBehaviourStateIndex == 2 && ((NetworkBehaviour)__instance).IsOwner && !((EnemyAI)__instance).isEnemyDead && ((EnemyAI)__instance).ventAnimationFinished) { PlayerControllerB val = ((other != null) ? ((Component)other).GetComponent() : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)((EnemyAI)__instance).targetPlayer) && !inKillAnimation.Invoke(__instance) && !(((EnemyAI)__instance).stunNormalizedTimer >= 0f) && ((EnemyAI)__instance).PlayerIsTargetable(val, false, false, true) && !(bool)isSeparatedByMineshaftElevator.Invoke(__instance, new object[1] { ((Component)val).transform.position })) { __instance.KillPlayerServerRpc((int)val.playerClientId); } } } } [HarmonyPatch(typeof(EnemyAI), "SwitchToBehaviourState")] internal static class JesterStateTransitionPatch { [HarmonyPostfix] private static void Postfix(EnemyAI __instance, int stateIndex) { if (stateIndex == 2 && __instance is JesterAI && ((NetworkBehaviour)__instance).IsServer && ((NetworkBehaviour)__instance).IsOwner && !((Object)(object)__instance.targetPlayer == (Object)null) && !((Object)(object)__instance.targetPlayer == (Object)(object)GameNetworkManager.Instance.localPlayerController)) { __instance.ChangeOwnershipOfEnemy(__instance.targetPlayer.actualClientId); } } } [HarmonyPatch(typeof(KnifeItem), "HitKnife")] internal static class KnifeAttackPatch { private const int QueryTriggerIgnore = 1; private const int QueryTriggerUseGlobal = 0; private const sbyte QueryTriggerIgnore_SByte = 1; [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { List list = new List(instructions); for (int i = 0; i < list.Count; i++) { CodeInstruction val = list[i]; if (BalanceTweaksPlugin.EnableKnifeLinecastBlock.Value && i + 1 < list.Count && list[i + 1].opcode == OpCodes.Call && list[i + 1].operand is MethodInfo { Name: "Linecast", IsStatic: not false }) { if (val.opcode == OpCodes.Ldc_I4_1) { val.opcode = OpCodes.Ldc_I4_0; val.operand = null; } else if (val.opcode == OpCodes.Ldc_I4_S && val.operand is sbyte b && b == 1) { val.operand = (sbyte)0; } else if (val.opcode == OpCodes.Ldc_I4 && val.operand is int num && num == 1) { val.operand = 0; } } } return list; } } [HarmonyPatch(typeof(KnifeItem), "HitKnife")] internal static class KnifeTriggerPatch { private static readonly MethodInfo IsTriggerGetter = AccessTools.PropertyGetter(typeof(Collider), "isTrigger"); [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { if (!BalanceTweaksPlugin.EnableKnifeTriggerHit.Value) { return instructions; } List list = new List(instructions); for (int i = 0; i < list.Count; i++) { if (!(list[i].opcode == OpCodes.Callvirt) || !(list[i].operand is MethodInfo methodInfo) || !(methodInfo == IsTriggerGetter)) { continue; } list[i].opcode = OpCodes.Pop; list[i].operand = null; for (int j = i + 1; j < list.Count; j++) { if (list[j].opcode == OpCodes.Brtrue || list[j].opcode == OpCodes.Brtrue_S) { list[j].opcode = OpCodes.Nop; list[j].operand = null; break; } } break; } return list; } } [HarmonyPatch(typeof(StartOfRound), "Start")] internal static class KnifeWeightPatch { [HarmonyPostfix] private static void ModifyKnifeWeight() { if (!BalanceTweaksPlugin.KnifeWeight.Value) { return; } foreach (Item items in StartOfRound.Instance.allItemsList.itemsList) { if (items.itemName == "Kitchen knife") { items.weight = 1.05f; break; } } } } [HarmonyPatch(typeof(NetworkManager), "SetSingleton")] internal static class NetworkPrefabPatch { private static void Postfix() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_0034: 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) if (!BalanceTweaksPlugin.CreateNetworkPrefab.Value || (Object)(object)NetworkManager.Singleton == (Object)null || NetworkManager.Singleton.PrefabHandler == null) { return; } GameObject val = new GameObject("LKKBalanceTweaks Prefab"); ((Object)val).hideFlags = (HideFlags)(((Object)val).hideFlags | 0x3D); Object.DontDestroyOnLoad((Object)(object)val); NetworkObject obj = val.AddComponent(); try { FieldInfo field = typeof(NetworkObject).GetField("GlobalObjectIdHash", BindingFlags.Instance | BindingFlags.NonPublic); if (field != null) { uint hashCode = (uint)"LKKBalanceTweaks".GetHashCode(); field.SetValue(obj, hashCode); } } catch { } NetworkManager.Singleton.PrefabHandler.AddNetworkPrefab(val); } } [HarmonyPatch(typeof(StartOfRound), "Start")] internal static class ShotgunConductivePatch { [HarmonyPostfix] private static void MakeShotgunConductive() { if (!BalanceTweaksPlugin.ShotgunConductive.Value || !((NetworkBehaviour)StartOfRound.Instance).IsHost) { return; } foreach (Item items in StartOfRound.Instance.allItemsList.itemsList) { if (items.itemName == "Shotgun") { items.isConductiveMetal = true; break; } } } } [HarmonyPatch(typeof(ShotgunItem), "ShootGun")] internal static class ShotgunEnemyDamagePatch { private const float OriginalRangeThreshold = 3.7f; private const int OriginalDamageClose = 5; private const int OriginalDamageMedium = 3; private const int OriginalDamageFar = 2; private const int DamageClose = 4; private const int DamageMedium = 3; private const int DamageFar = 2; [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { int remainingMatches = 0; foreach (CodeInstruction instruction in instructions) { if (instruction.opcode == OpCodes.Ldc_R4 && Mathf.Approximately((float)instruction.operand, 3.7f)) { remainingMatches = 3; } if (remainingMatches > 0) { if (instruction.opcode == OpCodes.Ldc_I4_5) { instruction.opcode = OpCodes.Ldc_I4; instruction.operand = 4; remainingMatches--; } else if (instruction.opcode == OpCodes.Ldc_I4_3) { instruction.opcode = OpCodes.Ldc_I4; instruction.operand = 3; remainingMatches--; } else if (instruction.opcode == OpCodes.Ldc_I4_2) { instruction.opcode = OpCodes.Ldc_I4; instruction.operand = 2; remainingMatches--; } else if (instruction.opcode == OpCodes.Ldc_I4 && instruction.operand is int num && (num == 5 || num == 3 || num == 2)) { instruction.operand = num switch { 3 => 3, 5 => 4, _ => 2, }; remainingMatches--; } } yield return instruction; } } } [HarmonyPatch(typeof(EnemyAI), "HitEnemyServerRpc")] internal static class ShotgunEnemyDamageSyncPatch { private static readonly int[] OriginalDamage = new int[3] { 5, 3, 2 }; private static readonly int[] ModdedDamage = new int[3] { 4, 3, 2 }; [HarmonyPrefix] private static void SyncShotgunDamage(ref int force, int playerWhoHit) { if (!((NetworkBehaviour)StartOfRound.Instance).IsHost || playerWhoHit < 0 || playerWhoHit >= StartOfRound.Instance.allPlayerScripts.Length) { return; } PlayerControllerB val = StartOfRound.Instance.allPlayerScripts[playerWhoHit]; if ((Object)(object)val == (Object)null || !(val.currentlyHeldObjectServer is ShotgunItem)) { return; } int[] array = (BalanceTweaksPlugin.ShotgunEnemyDamage.Value ? OriginalDamage : ModdedDamage); int[] array2 = (BalanceTweaksPlugin.ShotgunEnemyDamage.Value ? ModdedDamage : OriginalDamage); for (int i = 0; i < array.Length; i++) { if (force == array[i]) { force = array2[i]; break; } } } } [HarmonyPatch(typeof(StartOfRound), "Start")] internal static class ShotgunWeightPatch { [HarmonyPostfix] private static void ModifyShotgunWeight() { if (!BalanceTweaksPlugin.ShotgunWeight.Value) { return; } foreach (Item items in StartOfRound.Instance.allItemsList.itemsList) { if (items.itemName == "Shotgun") { items.weight = 1.2f; break; } } } } [HarmonyPatch(typeof(Shovel), "HitShovel")] internal static class ShovelAttackPatch { private const float OriginalSphereCastRadius = 0.8f; private const float SphereCastRadius = 0.75f; private const float OriginalSphereCastDistance = 1.5f; private const float SphereCastDistance = 1.5f; private const float FloatComparisonEpsilon = 0.0001f; private const int QueryTriggerIgnore = 1; private const int QueryTriggerUseGlobal = 0; private const sbyte QueryTriggerIgnore_SByte = 1; [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { List list = new List(instructions); for (int i = 0; i < list.Count; i++) { CodeInstruction val = list[i]; if (BalanceTweaksPlugin.EnableShovelNarrowHitbox.Value && val.opcode == OpCodes.Ldc_R4 && val.operand is float num) { if (Math.Abs(num - 0.8f) < 0.0001f) { val.operand = 0.75f; } else if (Math.Abs(num - 1.5f) < 0.0001f) { val.operand = 1.5f; } } if (BalanceTweaksPlugin.EnableShovelLinecastBlock.Value && i + 1 < list.Count && list[i + 1].opcode == OpCodes.Call && list[i + 1].operand is MethodInfo { Name: "Linecast", IsStatic: not false }) { if (val.opcode == OpCodes.Ldc_I4_1) { val.opcode = OpCodes.Ldc_I4_0; val.operand = null; } else if (val.opcode == OpCodes.Ldc_I4_S && val.operand is sbyte b && b == 1) { val.operand = (sbyte)0; } else if (val.opcode == OpCodes.Ldc_I4 && val.operand is int num2 && num2 == 1) { val.operand = 0; } } } return list; } } [HarmonyPatch(typeof(Shovel), "HitShovel")] internal static class ShovelTriggerPatch { private static readonly MethodInfo IsTriggerGetter = AccessTools.PropertyGetter(typeof(Collider), "isTrigger"); [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { if (!BalanceTweaksPlugin.EnableShovelTriggerHit.Value) { return instructions; } List list = new List(instructions); for (int i = 0; i < list.Count; i++) { if (!(list[i].opcode == OpCodes.Callvirt) || !(list[i].operand is MethodInfo methodInfo) || !(methodInfo == IsTriggerGetter)) { continue; } list[i].opcode = OpCodes.Pop; list[i].operand = null; for (int j = i + 1; j < list.Count; j++) { if (list[j].opcode == OpCodes.Brtrue || list[j].opcode == OpCodes.Brtrue_S) { list[j].opcode = OpCodes.Nop; list[j].operand = null; break; } } break; } return list; } } [HarmonyPatch(typeof(StartOfRound), "Start")] internal static class ShovelWeightPatch { [HarmonyPostfix] private static void ModifyShovelWeight() { if (!BalanceTweaksPlugin.ShovelWeight.Value) { return; } foreach (Item items in StartOfRound.Instance.allItemsList.itemsList) { if (items.itemName == "Shovel") { items.weight = 1.18f; break; } } } } [HarmonyPatch(typeof(PlayerControllerB), "ScrollMouse_performed")] internal static class BlockScrollSwitchPatch { private const float KnifeCooldownTime = 0.43f; private static readonly FieldInfo knifeCooldownField = AccessTools.Field(typeof(KnifeItem), "timeAtLastDamageDealt"); [HarmonyPrefix] private static bool Prefix(PlayerControllerB __instance) { GrabbableObject currentlyHeldObjectServer = __instance.currentlyHeldObjectServer; Shovel val = (Shovel)(object)((currentlyHeldObjectServer is Shovel) ? currentlyHeldObjectServer : null); if ((Object)(object)val != (Object)null && val.reelingUp) { return false; } GrabbableObject currentlyHeldObjectServer2 = __instance.currentlyHeldObjectServer; KnifeItem val2 = (KnifeItem)(object)((currentlyHeldObjectServer2 is KnifeItem) ? currentlyHeldObjectServer2 : null); if ((Object)(object)val2 != (Object)null && Time.realtimeSinceStartup - (float)knifeCooldownField.GetValue(val2) < 0.43f) { return false; } return true; } } [HarmonyPatch(typeof(PlayerControllerB), "UseUtilitySlot_performed")] internal static class BlockUtilitySlotSwitchPatch { private const float KnifeCooldownTime = 0.43f; private static readonly FieldInfo knifeCooldownField = AccessTools.Field(typeof(KnifeItem), "timeAtLastDamageDealt"); [HarmonyPrefix] private static bool Prefix(PlayerControllerB __instance) { GrabbableObject currentlyHeldObjectServer = __instance.currentlyHeldObjectServer; Shovel val = (Shovel)(object)((currentlyHeldObjectServer is Shovel) ? currentlyHeldObjectServer : null); if ((Object)(object)val != (Object)null && val.reelingUp) { return false; } GrabbableObject currentlyHeldObjectServer2 = __instance.currentlyHeldObjectServer; KnifeItem val2 = (KnifeItem)(object)((currentlyHeldObjectServer2 is KnifeItem) ? currentlyHeldObjectServer2 : null); if ((Object)(object)val2 != (Object)null && Time.realtimeSinceStartup - (float)knifeCooldownField.GetValue(val2) < 0.43f) { return false; } return true; } } }