using System; using System.Collections.Generic; using System.Reflection.Emit; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using UnityEngine; 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; _harmony.PatchAll(typeof(MechaDronesTweaks)); } } public static class MechaDronesTweaks { public static bool UseFixedSpeed; public static bool SkipStage1; public static bool RemoveSpeedLimitForStage1 = true; public static float FixedSpeed = 300f; public static float SpeedMultiplier = 4f; public static float EnergyMultiplier = 0.1f; [HarmonyTranspiler] [HarmonyPatch(typeof(ConstructionSystem), nameof(ConstructionSystem.UpdateDrones))] [HarmonyPatch(typeof(UIMechaWindow), nameof(UIMechaWindow.UpdateProps))] [HarmonyPatch(typeof(UITechTree), nameof(UITechTree.RefreshDataValueText))] private static IEnumerable UITechTreeRefreshDataValueText_Transpiler( IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(GameHistoryData), nameof(GameHistoryData.constructionDroneSpeed))) ); if (UseFixedSpeed) { matcher.Repeat(m => m.Advance(1).InsertAndAdvance( new CodeInstruction(OpCodes.Pop), new CodeInstruction(OpCodes.Ldc_R4, FixedSpeed) )); } else { matcher.Repeat(m => m.Advance(1).InsertAndAdvance( new CodeInstruction(OpCodes.Ldc_R4, SpeedMultiplier), new CodeInstruction(OpCodes.Mul) )); } return matcher.InstructionEnumeration(); } [HarmonyTranspiler] [HarmonyPatch(typeof(ConstructionModuleComponent), nameof(ConstructionModuleComponent.EjectBaseDrone))] [HarmonyPatch(typeof(ConstructionModuleComponent), nameof(ConstructionModuleComponent.EjectMechaDrone))] private static IEnumerable MechaUpdateTargets_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); if (!SkipStage1) return matcher.InstructionEnumeration(); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldc_I4_1), new CodeMatch(OpCodes.Stfld, AccessTools.Field(typeof(DroneComponent), nameof(DroneComponent.stage))) ).Opcode = OpCodes.Ldc_I4_2; return matcher.InstructionEnumeration(); } [HarmonyTranspiler, HarmonyPatch(typeof(ConstructionSystem), nameof(ConstructionSystem.UpdateDrones))] private static IEnumerable MechaUpdateDrones_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); if (EnergyMultiplier >= 1f) return matcher.InstructionEnumeration(); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(ModeConfig), nameof(ModeConfig.droneEnergyPerMeter))) ).Advance(1).Insert( new CodeInstruction(OpCodes.Ldc_R8, (double)EnergyMultiplier), new CodeInstruction(OpCodes.Mul) ); return matcher.InstructionEnumeration(); } [HarmonyTranspiler] // ref CraftData craft, PlanetFactory factory, ref Vector3 ejectPos, float droneSpeed, float dt, ref double mechaEnergy, ref double mechaEnergyChange, double flyEnergyRate, double repairEnergyCost, out float energyRatio [HarmonyPatch(typeof(DroneComponent), nameof(DroneComponent.InternalUpdate), new[] { typeof(CraftData), typeof(PlanetFactory), typeof(Vector3), typeof(float), typeof(float), typeof(double), typeof(double), typeof(double), typeof(double), typeof(float) }, new[] { ArgumentType.Ref, ArgumentType.Normal, ArgumentType.Ref, ArgumentType.Normal, ArgumentType.Normal, ArgumentType.Ref, ArgumentType.Ref, ArgumentType.Normal, ArgumentType.Normal, ArgumentType.Out })] // ref CraftData craft, PlanetFactory factory, Vector3 ejectPos, float droneSpeed, float dt, ref long energy, double flyEnergyRate, double repairEnergyCost, out float energyRatio [HarmonyPatch(typeof(DroneComponent), nameof(DroneComponent.InternalUpdate), new[] { typeof(CraftData), typeof(PlanetFactory), typeof(Vector3), typeof(float), typeof(float), typeof(long), typeof(double), typeof(double), typeof(float) }, new[] { ArgumentType.Ref, ArgumentType.Normal, ArgumentType.Normal, ArgumentType.Normal, ArgumentType.Normal, ArgumentType.Ref, ArgumentType.Normal, ArgumentType.Normal, ArgumentType.Out })] private static IEnumerable MechaDroneUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); if (RemoveSpeedLimitForStage1) { matcher.MatchForward(false, new CodeMatch(instr => instr.opcode == OpCodes.Ldc_R4 && instr.OperandIs(1f)), new CodeMatch(instr => instr.opcode == OpCodes.Ldc_R4 && instr.OperandIs(3f)) ); matcher.Advance(1).Operand = 10000f; } if (!UseFixedSpeed && Math.Abs(SpeedMultiplier - 1.0f) < 0.01f) return matcher.InstructionEnumeration(); matcher.Start().MatchForward(false, new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(OpCodes.Ldc_R4), new CodeMatch(OpCodes.Stfld, AccessTools.Field(typeof(DroneComponent), nameof(DroneComponent.progress))) ); matcher.Repeat(m => { if (m.InstructionAt(1).OperandIs(0f)) { m.InstructionAt(3).labels = m.Labels; m.RemoveInstructions(3); } else if (m.InstructionAt(1).OperandIs(1f)) { m.Advance(1).Operand = 0f; m.Advance(2); } } ); matcher.Start().MatchForward(false, new CodeMatch(instr => instr.opcode == OpCodes.Ldc_R4 && instr.OperandIs(0.5f)) ); matcher.Repeat(m => { if (UseFixedSpeed) { if (FixedSpeed > 75f) { m.Operand = 0.5f * FixedSpeed / 75f; } } else { m.Operand = 0.5f * SpeedMultiplier; } } ); return matcher.InstructionEnumeration(); } }