using System; using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; using BepInEx; using BepInEx.Configuration; using HarmonyLib; namespace MechaDronesTweaks; [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] [BepInDependency(FastDronesRemover.FastDronesGuid, BepInDependency.DependencyFlags.SoftDependency)] public class MechaDronesTweaksPlugin : BaseUnityPlugin { public new static readonly BepInEx.Logging.ManualLogSource Logger = BepInEx.Logging.Logger.CreateLogSource(PluginInfo.PLUGIN_NAME); private readonly Harmony _harmony = new(PluginInfo.PLUGIN_GUID); public MechaDronesTweaksPlugin() { /* Remove FastDrones MOD if loaded */ try { if (FastDronesRemover.Run(_harmony)) { Logger.LogInfo("Unpatch FastDrones - OK"); } } catch (Exception e) { Logger.LogWarning($"Failed to unpatch FastDrones: {e}"); } } public void Awake() { MechaDronesTweaks.UseFixedSpeed = Config.Bind("MechaDrones", "UseFixedSpeed", MechaDronesTweaks.UseFixedSpeed, "Use fixed speed for mecha drones").Value; MechaDronesTweaks.SkipStage1 = Config.Bind("MechaDrones", "SkipStage1", MechaDronesTweaks.SkipStage1, "Skip 1st stage of working mecha drones (flying away from mecha in ~1/3 speed for several frames)").Value; MechaDronesTweaks.RemoveSpeedLimitForStage1 = Config.Bind("MechaDrones", "RemoveSpeedLimitForStage1", MechaDronesTweaks.RemoveSpeedLimitForStage1, "Remove speed limit for 1st stage (has a speed limit @ ~10m/s originally)").Value; MechaDronesTweaks.FixedSpeed = Config.Bind("MechaDrones", "FixedSpeed", MechaDronesTweaks.FixedSpeed, new ConfigDescription("Fixed speed for mecha drones, working only when UseFixedSpeed is enabled", new AcceptableValueRange(6f, 1000f))).Value; MechaDronesTweaks.SpeedMultiplier = Config.Bind("MechaDrones", "SpeedMultiplier", MechaDronesTweaks.SpeedMultiplier, new ConfigDescription("Speed multiplier for mecha drones, working only when UseFixedSpeed is disabled", new AcceptableValueRange(1f, 10f))).Value; MechaDronesTweaks.EnergyMultiplier = Config.Bind("MechaDrones", "EnergyMultiplier", MechaDronesTweaks.EnergyMultiplier, new ConfigDescription("Energy consumption multiplier for mecha drones", new AcceptableValueRange(0f, 1f))).Value; MechaDronesTweaks.EnergyMultiplier = Config.Bind("MechaDrones", "EnergyMultiplier", MechaDronesTweaks.EnergyMultiplier, new ConfigDescription("Energy consumption multiplier for mecha drones", new AcceptableValueRange(0f, 1f))).Value; MechaDronesTweaks.removeBuildRangeLimit = Config.Bind("MechaBuild", "RemoveBuildRangeLimit", MechaDronesTweaks.removeBuildRangeLimit, "Remove limit for build range and maximum count of drag building belts/buildings\nNote: this does not affect range limit for mecha drones' action") .Value; MechaDronesTweaks.largerAreaForUpgradeAndDismantle = Config.Bind("MechaBuild", "LargerAreaForUpgradeAndDismantle", MechaDronesTweaks.largerAreaForUpgradeAndDismantle, "Increase maximum area size for upgrade and dismantle to 31x31 (from 11x11)").Value; MechaDronesTweaks.largerAreaForTerraform = Config.Bind("MechaBuild", "LargerAreaForTerraform", MechaDronesTweaks.largerAreaForTerraform, "Increase maximum area size for terraform to 30x30 (from 10x10)\nNote: this may impact game performance while using large area") .Value; _harmony.PatchAll(typeof(MechaDronesTweaks)); } } public static class MechaDronesTweaks { public static bool UseFixedSpeed = false; public static bool SkipStage1 = false; public static bool RemoveSpeedLimitForStage1 = true; public static float FixedSpeed = 300f; public static float SpeedMultiplier = 4f; public static float EnergyMultiplier = 0.1f; public static bool removeBuildRangeLimit = true; public static bool largerAreaForUpgradeAndDismantle = true; public static bool largerAreaForTerraform = true; [HarmonyTranspiler, HarmonyPatch(typeof(UITechTree), "RefreshDataValueText")] private static IEnumerable UITechTreeRefreshDataValueText_Transpiler( IEnumerable instructions) { foreach (var instr in instructions) { if (instr.opcode == OpCodes.Callvirt && instr.OperandIs(AccessTools.Method(typeof(Mecha), "get_droneSpeed"))) { if (UseFixedSpeed) { yield return new CodeInstruction(OpCodes.Pop); yield return new CodeInstruction(OpCodes.Ldc_R4, FixedSpeed); } else { yield return instr; yield return new CodeInstruction(OpCodes.Ldc_R4, SpeedMultiplier); yield return new CodeInstruction(OpCodes.Mul); } } else { yield return instr; } } } [HarmonyTranspiler, HarmonyPatch(typeof(MechaDroneLogic), "UpdateTargets")] private static IEnumerable MechaUpdateTargets_Transpiler(IEnumerable instructions) { if (SkipStage1) { var ilist = instructions.ToList(); for (var i = 0; i < ilist.Count; i++) { var instr = ilist[i]; if (instr.opcode == OpCodes.Ldc_I4_1) { var instrNext = ilist[i + 1]; if (instrNext.opcode == OpCodes.Stfld && instrNext.OperandIs(AccessTools.Field(typeof(MechaDrone), "stage"))) { instr.opcode = OpCodes.Ldc_I4_2; } } yield return instr; } } else { foreach (var instr in instructions) { yield return instr; } } } [HarmonyTranspiler, HarmonyPatch(typeof(MechaDroneLogic), "UpdateDrones")] private static IEnumerable MechaUpdateDrones_Transpiler(IEnumerable instructions) { if (EnergyMultiplier >= 1f) { foreach (var instr in instructions) { yield return instr; } } else { foreach (var instr in instructions) { yield return instr; if (instr.opcode != OpCodes.Ldfld || !instr.OperandIs(AccessTools.Field(typeof(Mecha), "droneEnergyPerMeter"))) continue; yield return new CodeInstruction(OpCodes.Ldc_R8, (double)EnergyMultiplier); yield return new CodeInstruction(OpCodes.Mul); } } } [HarmonyTranspiler, HarmonyPatch(typeof(MechaDrone), "Update")] private static IEnumerable MechaDroneUpdate_Transpiler(IEnumerable instructions) { if (!UseFixedSpeed && Math.Abs(SpeedMultiplier - 1.0f) < 0.01f) { foreach (var instr in instructions) { yield return instr; } } else { var ilist = instructions.ToList(); for (var i = 0; i < ilist.Count; i++) { var instr = ilist[i]; if (instr.opcode == OpCodes.Ldarg_0) { var instrNext = ilist[i + 1]; if (instrNext.opcode == OpCodes.Ldfld && instrNext.OperandIs(AccessTools.Field(typeof(MechaDrone), "speed"))) { if (UseFixedSpeed) { var newInstr = new CodeInstruction(instr) { opcode = OpCodes.Ldc_R4, operand = FixedSpeed }; yield return newInstr; } else { yield return instr; yield return instrNext; yield return new CodeInstruction(OpCodes.Ldc_R4, SpeedMultiplier); yield return new CodeInstruction(OpCodes.Mul); } i++; continue; } if (instrNext.opcode == OpCodes.Ldc_R4) { if (instrNext.OperandIs(0f)) { var instrNext2 = ilist[i + 2]; if (instrNext2.opcode == OpCodes.Stfld && instrNext2.OperandIs(AccessTools.Field(typeof(MechaDrone), "progress"))) { ilist[i + 3].labels = instr.labels; i += 2; continue; } } else if (instrNext.OperandIs(1f)) { var instrNext2 = ilist[i + 2]; if (instrNext2.opcode == OpCodes.Stfld && instrNext2.OperandIs(AccessTools.Field(typeof(MechaDrone), "progress"))) { instrNext.operand = 0f; yield return instr; yield return instrNext; yield return instrNext2; i += 2; continue; } } } } else if (instr.opcode == OpCodes.Ldc_R4) { if (instr.OperandIs(0.5f)) { if (UseFixedSpeed) { if (FixedSpeed > 75f) { instr.operand = 0.5f * FixedSpeed / 75f; } } else { instr.operand = 0.5f * SpeedMultiplier; } } else if (instr.OperandIs(3f)) { if (RemoveSpeedLimitForStage1) { instr.operand = 10000f; } } } yield return instr; } } } [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Dismantle), nameof(BuildTool_Dismantle.DeterminePreviews))] [HarmonyPatch(typeof(BuildTool_Upgrade), nameof(BuildTool_Upgrade.DeterminePreviews))] private static IEnumerable BuildTools_CursorSizePatch_Transpiler( IEnumerable instructions) { if (!largerAreaForUpgradeAndDismantle) { foreach (var instr in instructions) { yield return instr; } } else { foreach (var instr in instructions) { if (instr.opcode == OpCodes.Ldc_I4_S && instr.OperandIs(11)) { yield return new CodeInstruction(OpCodes.Ldc_I4_S, 31); } else { yield return instr; } } } } [HarmonyTranspiler, HarmonyPatch(typeof(BuildTool_Reform), MethodType.Constructor)] private static IEnumerable BuildTool_Reform_Constructor_Transpiler( IEnumerable instructions) { if (!largerAreaForTerraform) { foreach (var instr in instructions) { yield return instr; } } else { foreach (var instr in instructions) { if (instr.opcode == OpCodes.Ldc_I4_S && instr.OperandIs(100)) { yield return new CodeInstruction(OpCodes.Ldc_I4, 900); } else { yield return instr; } } } } [HarmonyTranspiler, HarmonyPatch(typeof(BuildTool_Reform), nameof(BuildTool_Reform.ReformAction))] private static IEnumerable BuildTool_Reform_ReformAction_Transpiler( IEnumerable instructions) { if (!largerAreaForTerraform) { foreach (var instr in instructions) { yield return instr; } } else { var ilist = instructions.ToList(); for (var i = 0; i < ilist.Count; i++) { var instr = ilist[i]; if (instr.opcode == OpCodes.Ldc_I4_S && instr.OperandIs(10) && (ilist[i - 1].opcode == OpCodes.Ldfld && ilist[i - 1].OperandIs(AccessTools.Field(typeof(BuildTool_Reform), "brushSize")) || ilist[i + 1].opcode == OpCodes.Stfld && ilist[i + 1].OperandIs(AccessTools.Field(typeof(BuildTool_Reform), "brushSize"))) ) { yield return new CodeInstruction(OpCodes.Ldc_I4_S, 30); } else { yield return instr; } } } } [HarmonyTranspiler, HarmonyPatch(typeof(ConnGizmoGraph), MethodType.Constructor)] private static IEnumerable ConnGizmoGraph_Constructor_Transpiler( IEnumerable instructions) { if (!removeBuildRangeLimit) { foreach (var instr in instructions) { yield return instr; } } else { foreach (var instr in instructions) { if (instr.opcode == OpCodes.Ldc_I4 && instr.OperandIs(256)) { yield return new CodeInstruction(OpCodes.Ldc_I4, 2048); } else { yield return instr; } } } } [HarmonyTranspiler, HarmonyPatch(typeof(ConnGizmoGraph), nameof(ConnGizmoGraph.SetPointCount))] private static IEnumerable ConnGizmoGraph_SetPointCount_Transpiler( IEnumerable instructions) { if (!removeBuildRangeLimit) { foreach (var instr in instructions) { yield return instr; } } else { foreach (var instr in instructions) { if (instr.opcode == OpCodes.Ldc_I4 && instr.OperandIs(256)) { yield return new CodeInstruction(OpCodes.Ldc_I4, 2048); } else { yield return instr; } } } } [HarmonyTranspiler, HarmonyPatch(typeof(BuildTool_Path), nameof(BuildTool_Path._OnInit))] private static IEnumerable BuildTool_Path__OnInit_Transpiler( IEnumerable instructions) { if (!removeBuildRangeLimit) { foreach (var instr in instructions) { yield return instr; } } else { foreach (var instr in instructions) { if (instr.opcode == OpCodes.Ldc_I4 && instr.OperandIs(160)) { instr.operand = 2048; } yield return instr; } } } [HarmonyTranspiler, HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click._OnInit))] private static IEnumerable BuildTool_Click__OnInit_Transpiler( IEnumerable instructions) { if (!removeBuildRangeLimit) { foreach (var instr in instructions) { yield return instr; } } else { foreach (var instr in instructions) { if (instr.opcode == OpCodes.Ldc_I4_S && instr.OperandIs(15)) { yield return new CodeInstruction(OpCodes.Ldc_I4, 512); } else { yield return instr; } } } } [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Addon), nameof(BuildTool_Addon.CheckBuildConditions))] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click.CheckBuildConditions))] [HarmonyPatch(typeof(BuildTool_Dismantle), nameof(BuildTool_Dismantle.DetermineMoreChainTargets))] [HarmonyPatch(typeof(BuildTool_Dismantle), nameof(BuildTool_Dismantle.DeterminePreviews))] [HarmonyPatch(typeof(BuildTool_Inserter), nameof(BuildTool_Inserter.CheckBuildConditions))] [HarmonyPatch(typeof(BuildTool_Path), nameof(BuildTool_Path.CheckBuildConditions))] [HarmonyPatch(typeof(BuildTool_Reform), nameof(BuildTool_Reform.ReformAction))] [HarmonyPatch(typeof(BuildTool_Upgrade), nameof(BuildTool_Upgrade.DetermineMoreChainTargets))] [HarmonyPatch(typeof(BuildTool_Upgrade), nameof(BuildTool_Upgrade.DeterminePreviews))] private static IEnumerable BuildAreaLimitRemoval_Transpiler( IEnumerable instructions) { if (!removeBuildRangeLimit) { foreach (var instr in instructions) { yield return instr; } } else { /* Patch (player.mecha.buildArea * player.mecha.buildArea) to 100000000 */ var ilist = instructions.ToList(); var count = ilist.Count - 8; int i; for (i = 0; i < count; i++) { var found = false; while (true) { var instr = ilist[i + 1]; if (instr.opcode != OpCodes.Call || !instr.OperandIs(AccessTools.Method(typeof(BuildTool), "get_player"))) { break; } instr = ilist[i + 2]; if (instr.opcode != OpCodes.Callvirt || !instr.OperandIs(AccessTools.Method(typeof(Player), "get_mecha"))) { break; } instr = ilist[i + 3]; if (instr.opcode != OpCodes.Ldfld || !instr.OperandIs(AccessTools.Field(typeof(Mecha), "buildArea"))) { break; } instr = ilist[i + 5]; if (instr.opcode != OpCodes.Call || !instr.OperandIs(AccessTools.Method(typeof(BuildTool), "get_player"))) { break; } instr = ilist[i + 6]; if (instr.opcode != OpCodes.Callvirt || !instr.OperandIs(AccessTools.Method(typeof(Player), "get_mecha"))) { break; } instr = ilist[i + 7]; if (instr.opcode != OpCodes.Ldfld || !instr.OperandIs(AccessTools.Field(typeof(Mecha), "buildArea"))) { break; } instr = ilist[i + 8]; if (instr.opcode != OpCodes.Mul) { break; } found = true; yield return new CodeInstruction(OpCodes.Ldc_R4, 100000000.0f); break; } if (found) { i += 8; } else { yield return ilist[i]; } } for (; i < ilist.Count; i++) { yield return ilist[i]; } } } }