using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using BepInEx.Configuration; using CommonAPI.Systems; using HarmonyLib; using UnityEngine; using UnityEngine.UI; using UXAssist.Common; using GameLogicProc = UXAssist.Common.GameLogic; namespace UXAssist.Patches; public class FactoryPatch : PatchImpl { public static ConfigEntry UnlimitInteractiveEnabled; public static ConfigEntry RemoveSomeConditionEnabled; public static ConfigEntry NightLightEnabled; public static ConfigEntry NightLightAngleX; public static ConfigEntry NightLightAngleY; public static ConfigEntry RemoveBuildRangeLimitEnabled; public static ConfigEntry LargerAreaForUpgradeAndDismantleEnabled; public static ConfigEntry LargerAreaForTerraformEnabled; public static ConfigEntry OffGridBuildingEnabled; public static ConfigEntry TreatStackingAsSingleEnabled; public static ConfigEntry QuickBuildAndDismantleLabsEnabled; public static ConfigEntry ProtectVeinsFromExhaustionEnabled; public static ConfigEntry DoNotRenderEntitiesEnabled; public static ConfigEntry DragBuildPowerPolesEnabled; public static ConfigEntry DragBuildPowerPolesAlternatelyEnabled; public static ConfigEntry BeltSignalsForBuyOutEnabled; public static ConfigEntry TankFastFillInAndTakeOutEnabled; public static ConfigEntry TankFastFillInAndTakeOutMultiplier; public static ConfigEntry CutConveyorBeltEnabled; public static ConfigEntry TweakBuildingBufferEnabled; public static ConfigEntry AssemblerBufferTimeMultiplier; public static ConfigEntry AssemblerBufferMininumMultiplier; public static ConfigEntry LabBufferMaxCountForAssemble; public static ConfigEntry LabBufferExtraCountForAdvancedAssemble; public static ConfigEntry LabBufferMaxCountForResearch; public static ConfigEntry ReceiverBufferCount; public static ConfigEntry EjectorBufferCount; public static ConfigEntry SiloBufferCount; public static ConfigEntry ShortcutKeysForBlueprintCopyEnabled; private static PressKeyBind _doNotRenderEntitiesKey; private static PressKeyBind _offgridfForPathsKey; private static PressKeyBind _cutConveyorBeltKey; private static PressKeyBind _dismantleBlueprintSelectionKey; private static PressKeyBind _selectAllBuildingsInBlueprintCopyKey; private static int _tankFastFillInAndTakeOutMultiplierRealValue = 2; public static void Init() { _doNotRenderEntitiesKey = KeyBindings.RegisterKeyBinding(new BuiltinKey { key = new CombineKey(0, 0, ECombineKeyAction.OnceClick, true), conflictGroup = KeyBindConflict.MOVEMENT | KeyBindConflict.FLYING | KeyBindConflict.BUILD_MODE_1 | KeyBindConflict.KEYBOARD_KEYBIND, name = "ToggleDoNotRenderEntities", canOverride = true } ); I18N.Add("KEYToggleDoNotRenderEntities", "[UXA] Toggle Do Not Render Factory Entities", "[UXA] 切换不渲染工厂建筑实体"); _offgridfForPathsKey = KeyBindings.RegisterKeyBinding(new BuiltinKey { key = new CombineKey(0, 0, ECombineKeyAction.OnceClick, true), conflictGroup = KeyBindConflict.MOVEMENT | KeyBindConflict.UI | KeyBindConflict.FLYING | KeyBindConflict.BUILD_MODE_1 | KeyBindConflict.KEYBOARD_KEYBIND, name = "OffgridForPaths", canOverride = true } ); I18N.Add("KEYOffgridForPaths", "[UXA] Build belts offgrid", "[UXA] 脱离网格建造传送带"); _cutConveyorBeltKey = KeyBindings.RegisterKeyBinding(new BuiltinKey { key = new CombineKey((int)KeyCode.X, CombineKey.ALT_COMB, ECombineKeyAction.OnceClick, false), conflictGroup = KeyBindConflict.MOVEMENT | KeyBindConflict.FLYING | KeyBindConflict.SAILING | KeyBindConflict.BUILD_MODE_1 | KeyBindConflict.KEYBOARD_KEYBIND, name = "CutConveyorBelt", canOverride = true } ); I18N.Add("KEYCutConveyorBelt", "[UXA] Cut conveyor belt", "[UXA] 切割传送带"); _dismantleBlueprintSelectionKey = KeyBindings.RegisterKeyBinding(new BuiltinKey { key = new CombineKey((int)KeyCode.X, CombineKey.CTRL_COMB, ECombineKeyAction.OnceClick, false), conflictGroup = KeyBindConflict.KEYBOARD_KEYBIND, name = "DismantleBlueprintSelection", canOverride = true } ); I18N.Add("KEYDismantleBlueprintSelection", "[UXA] Dismantle blueprint selected buildings", "[UXA] 拆除蓝图选中的建筑"); _selectAllBuildingsInBlueprintCopyKey = KeyBindings.RegisterKeyBinding(new BuiltinKey { key = new CombineKey((int)KeyCode.A, CombineKey.CTRL_COMB, ECombineKeyAction.OnceClick, false), conflictGroup = KeyBindConflict.KEYBOARD_KEYBIND, name = "SelectAllBuildingsInBlueprintCopy", canOverride = true } ); I18N.Add("KEYSelectAllBuildingsInBlueprintCopy", "[UXA] Select all buildings in Blueprint Copy Mode", "[UXA] 蓝图复制时选择所有建筑"); BeltSignalsForBuyOut.InitPersist(); ProtectVeinsFromExhaustion.InitConfig(); UnlimitInteractiveEnabled.SettingChanged += (_, _) => UnlimitInteractive.Enable(UnlimitInteractiveEnabled.Value); RemoveSomeConditionEnabled.SettingChanged += (_, _) => RemoveSomeConditionBuild.Enable(RemoveSomeConditionEnabled.Value); NightLightEnabled.SettingChanged += (_, _) => NightLight.Enable(NightLightEnabled.Value); NightLightAngleX.SettingChanged += (_, _) => NightLight.UpdateSunlightAngle(); NightLightAngleY.SettingChanged += (_, _) => NightLight.UpdateSunlightAngle(); RemoveBuildRangeLimitEnabled.SettingChanged += (_, _) => RemoveBuildRangeLimit.Enable(RemoveBuildRangeLimitEnabled.Value); LargerAreaForUpgradeAndDismantleEnabled.SettingChanged += (_, _) => LargerAreaForUpgradeAndDismantle.Enable(LargerAreaForUpgradeAndDismantleEnabled.Value); LargerAreaForTerraformEnabled.SettingChanged += (_, _) => LargerAreaForTerraform.Enable(LargerAreaForTerraformEnabled.Value); OffGridBuildingEnabled.SettingChanged += (_, _) => OffGridBuilding.Enable(OffGridBuildingEnabled.Value); TreatStackingAsSingleEnabled.SettingChanged += (_, _) => TreatStackingAsSingle.Enable(TreatStackingAsSingleEnabled.Value); QuickBuildAndDismantleLabsEnabled.SettingChanged += (_, _) => QuickBuildAndDismantleLab.Enable(QuickBuildAndDismantleLabsEnabled.Value); ProtectVeinsFromExhaustionEnabled.SettingChanged += (_, _) => ProtectVeinsFromExhaustion.Enable(ProtectVeinsFromExhaustionEnabled.Value); DoNotRenderEntitiesEnabled.SettingChanged += (_, _) => DoNotRenderEntities.Enable(DoNotRenderEntitiesEnabled.Value); DragBuildPowerPolesEnabled.SettingChanged += (_, _) => DragBuildPowerPoles.Enable(DragBuildPowerPolesEnabled.Value); DragBuildPowerPolesAlternatelyEnabled.SettingChanged += (_, _) => DragBuildPowerPoles.AlternatelyChanged(); BeltSignalsForBuyOutEnabled.SettingChanged += (_, _) => BeltSignalsForBuyOut.Enable(BeltSignalsForBuyOutEnabled.Value); TankFastFillInAndTakeOutEnabled.SettingChanged += (_, _) => TankFastFillInAndTakeOut.Enable(TankFastFillInAndTakeOutEnabled.Value); TankFastFillInAndTakeOutMultiplier.SettingChanged += (_, _) => UpdateTankFastFillInAndTakeOutMultiplierRealValue(); TweakBuildingBufferEnabled.SettingChanged += (_, _) => TweakBuildingBuffer.Enable(TweakBuildingBufferEnabled.Value); AssemblerBufferTimeMultiplier.SettingChanged += (_, _) => TweakBuildingBuffer.RefreshAssemblerBufferMultipliers(); AssemblerBufferMininumMultiplier.SettingChanged += (_, _) => TweakBuildingBuffer.RefreshAssemblerBufferMultipliers(); LabBufferMaxCountForAssemble.SettingChanged += (_, _) => TweakBuildingBuffer.RefreshLabBufferMaxCountForAssemble(); LabBufferExtraCountForAdvancedAssemble.SettingChanged += (_, _) => TweakBuildingBuffer.RefreshLabBufferMaxCountForAssemble(); LabBufferMaxCountForResearch.SettingChanged += (_, _) => TweakBuildingBuffer.RefreshLabBufferMaxCountForResearch(); ReceiverBufferCount.SettingChanged += (_, _) => TweakBuildingBuffer.RefreshReceiverBufferCount(); EjectorBufferCount.SettingChanged += (_, _) => TweakBuildingBuffer.RefreshEjectorBufferCount(); SiloBufferCount.SettingChanged += (_, _) => TweakBuildingBuffer.RefreshSiloBufferCount(); } public static void Start() { UnlimitInteractive.Enable(UnlimitInteractiveEnabled.Value); RemoveSomeConditionBuild.Enable(RemoveSomeConditionEnabled.Value); NightLight.Enable(NightLightEnabled.Value); RemoveBuildRangeLimit.Enable(RemoveBuildRangeLimitEnabled.Value); LargerAreaForUpgradeAndDismantle.Enable(LargerAreaForUpgradeAndDismantleEnabled.Value); LargerAreaForTerraform.Enable(LargerAreaForTerraformEnabled.Value); OffGridBuilding.Enable(OffGridBuildingEnabled.Value); TreatStackingAsSingle.Enable(TreatStackingAsSingleEnabled.Value); QuickBuildAndDismantleLab.Enable(QuickBuildAndDismantleLabsEnabled.Value); ProtectVeinsFromExhaustion.Enable(ProtectVeinsFromExhaustionEnabled.Value); DoNotRenderEntities.Enable(DoNotRenderEntitiesEnabled.Value); DragBuildPowerPoles.Enable(DragBuildPowerPolesEnabled.Value); BeltSignalsForBuyOut.Enable(BeltSignalsForBuyOutEnabled.Value); TankFastFillInAndTakeOut.Enable(TankFastFillInAndTakeOutEnabled.Value); TweakBuildingBuffer.Enable(TweakBuildingBufferEnabled.Value); Enable(true); UpdateTankFastFillInAndTakeOutMultiplierRealValue(); } public static void Uninit() { Enable(false); TweakBuildingBuffer.Enable(false); TankFastFillInAndTakeOut.Enable(false); BeltSignalsForBuyOut.Enable(false); DragBuildPowerPoles.Enable(false); DoNotRenderEntities.Enable(false); ProtectVeinsFromExhaustion.Enable(false); QuickBuildAndDismantleLab.Enable(false); TreatStackingAsSingle.Enable(false); OffGridBuilding.Enable(false); LargerAreaForTerraform.Enable(false); LargerAreaForUpgradeAndDismantle.Enable(false); RemoveBuildRangeLimit.Enable(false); NightLight.Enable(false); RemoveSomeConditionBuild.Enable(false); UnlimitInteractive.Enable(false); BeltSignalsForBuyOut.UninitPersist(); } private static void UpdateTankFastFillInAndTakeOutMultiplierRealValue() { _tankFastFillInAndTakeOutMultiplierRealValue = Mathf.Max(1, TankFastFillInAndTakeOutMultiplier.Value) * 2; } public static void OnInputUpdate() { if (_doNotRenderEntitiesKey.keyValue) DoNotRenderEntitiesEnabled.Value = !DoNotRenderEntitiesEnabled.Value; if (CutConveyorBeltEnabled.Value && _cutConveyorBeltKey.keyValue) { var raycast = GameMain.mainPlayer.controller?.cmd.raycast; int beltId; if (raycast != null && raycast.castEntity.id > 0 && (beltId = raycast.castEntity.beltId) > 0) { var cargoTraffic = raycast.planet.factory.cargoTraffic; Functions.FactoryFunctions.CutConveyorBelt(cargoTraffic, beltId); } } if (ShortcutKeysForBlueprintCopyEnabled.Value) { if (_dismantleBlueprintSelectionKey.keyValue) Functions.FactoryFunctions.DismantleBlueprintSelectedBuildings(); if (_selectAllBuildingsInBlueprintCopyKey.keyValue) Functions.FactoryFunctions.SelectAllBuildingsInBlueprintCopy(); } } public static void Export(BinaryWriter w) { var storage = BeltSignalsForBuyOut.DarkFogItemsInVoid; for (var i = 0; i < 6; i++) w.Write(storage[i]); } public static void Import(BinaryReader r) { var storage = BeltSignalsForBuyOut.DarkFogItemsInVoid; for (var i = 0; i < 6; i++) storage[i] = r.ReadInt32(); } [HarmonyTranspiler] [HarmonyPatch(typeof(ConnGizmoGraph), MethodType.Constructor)] private static IEnumerable ConnGizmoGraph_Constructor_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(ci => ci.opcode == OpCodes.Ldc_I4 && ci.OperandIs(256)) ); matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4, 2048)); return matcher.InstructionEnumeration(); } [HarmonyTranspiler] [HarmonyPatch(typeof(ConnGizmoGraph), nameof(ConnGizmoGraph.SetPointCount))] private static IEnumerable ConnGizmoGraph_SetPointCount_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(ci => ci.opcode == OpCodes.Ldc_I4 && ci.OperandIs(256)) ); matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4, 2048)); return matcher.InstructionEnumeration(); } [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Path), nameof(BuildTool_Path._OnInit))] private static IEnumerable BuildTool_Path__OnInit_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(ci => ci.opcode == OpCodes.Ldc_I4 && ci.OperandIs(160)) ); matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4, 2048)); return matcher.InstructionEnumeration(); } [HarmonyTranspiler, HarmonyPatch(typeof(BuildTool_Reform), MethodType.Constructor)] private static IEnumerable BuildTool_Reform_Constructor_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(ci => ci.opcode == OpCodes.Ldc_I4_S && ci.OperandIs(100)) ); matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4, 900)); return matcher.InstructionEnumeration(); } public class NightLight : PatchImpl { private static bool _nightlightInitialized; private static bool _mechaOnEarth; private static AnimationState _sail; private static Light _sunlight; protected override void OnEnable() { GameLogicProc.OnGameEnd += GameMain_End_Postfix; } protected override void OnDisable() { GameLogicProc.OnGameEnd -= GameMain_End_Postfix; if (_sunlight) { _sunlight.transform.localEulerAngles = new Vector3(0f, 180f); _sunlight = null; } _sail = null; _mechaOnEarth = false; _nightlightInitialized = false; } private static void GameMain_End_Postfix() { if (_sunlight) { _sunlight.transform.localEulerAngles = new Vector3(0f, 180f); _sunlight = null; } _sail = null; _mechaOnEarth = false; _nightlightInitialized = false; } public static void UpdateSunlightAngle() { if (!_sunlight) return; _sunlight.transform.rotation = Quaternion.LookRotation(-GameMain.mainPlayer.transform.up + GameMain.mainPlayer.transform.forward * NightLightAngleX.Value / 10f + GameMain.mainPlayer.transform.right * NightLightAngleY.Value / 10f); } [HarmonyPostfix] [HarmonyPatch(typeof(GameData), nameof(GameData.ArriveStar))] public static void GameData_ArriveStar_Postfix() { _sunlight = GameMain.universeSimulator?.LocalStarSimulator()?.sunLight; } [HarmonyPrefix] [HarmonyPatch(typeof(GameData), nameof(GameData.LeaveStar))] public static void GameData_LeaveStar_Prefix() { _sunlight = null; } [HarmonyPostfix] [HarmonyPatch(typeof(GameMain), nameof(GameMain.LateUpdate))] public static void GameMain_LateUpdate_Postfix(GameMain __instance) { if (__instance.isMenuDemo || !__instance._running) return; if (!_nightlightInitialized) { if (!GameMain.mainPlayer.controller.model.gameObject.activeInHierarchy) return; if (_sail == null) _sail = GameMain.mainPlayer.animator.sails[GameMain.mainPlayer.animator.sailAnimIndex]; _sunlight = GameMain.universeSimulator?.LocalStarSimulator()?.sunLight; _nightlightInitialized = true; } var sailing = _sail && _sail.enabled; if (_mechaOnEarth) { if (!sailing) { UpdateSunlightAngle(); return; } _mechaOnEarth = false; if (!_sunlight) return; _sunlight.transform.localEulerAngles = new Vector3(0f, 180f); _sunlight = null; return; } if (sailing) return; _mechaOnEarth = true; } [HarmonyTranspiler] [HarmonyPatch(typeof(StarSimulator), nameof(StarSimulator.LateUpdate))] private static IEnumerable StarSimulator_LateUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); var label1 = generator.DefineLabel(); var label2 = generator.DefineLabel(); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(OpCodes.Call, AccessTools.PropertyGetter(typeof(Component), nameof(Component.transform))) ).InsertAndAdvance( new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(NightLight), nameof(_mechaOnEarth))), new CodeInstruction(OpCodes.Brfalse_S, label1), new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(GameMain), nameof(GameMain.mainPlayer))), new CodeInstruction(OpCodes.Callvirt, AccessTools.PropertyGetter(typeof(Player), nameof(Player.transform))), new CodeInstruction(OpCodes.Callvirt, AccessTools.PropertyGetter(typeof(Transform), nameof(Transform.up))), new CodeInstruction(OpCodes.Stloc_0), new CodeInstruction(OpCodes.Br_S, label2) ); matcher.Labels.Add(label1); matcher.MatchForward(false, new CodeMatch(OpCodes.Stloc_0) ).Advance(1).Labels.Add(label2); return matcher.InstructionEnumeration(); } [HarmonyTranspiler] [HarmonyPatch(typeof(PlanetSimulator), nameof(PlanetSimulator.LateUpdate))] private static IEnumerable PlanetSimulator_LateUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) { // var vec = (NightlightEnabled ? GameMain.mainPlayer.transform.up : (Quaternion.Inverse(localPlanet.runtimeRotation) * (__instance.planetData.star.uPosition - __instance.planetData.uPosition).normalized)); var matcher = new CodeMatcher(instructions, generator); var label1 = generator.DefineLabel(); var label2 = generator.DefineLabel(); matcher.MatchForward(false, new CodeMatch(OpCodes.Stloc_1) ).Advance(1).InsertAndAdvance( new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(NightLight), nameof(_mechaOnEarth))), new CodeInstruction(OpCodes.Brfalse_S, label1), new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(GameMain), nameof(GameMain.mainPlayer))), new CodeInstruction(OpCodes.Callvirt, AccessTools.PropertyGetter(typeof(Player), nameof(Player.transform))), new CodeInstruction(OpCodes.Callvirt, AccessTools.PropertyGetter(typeof(Transform), nameof(Transform.up))), new CodeInstruction(OpCodes.Stloc_2), new CodeInstruction(OpCodes.Br_S, label2) ); matcher.Labels.Add(label1); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldsfld, AccessTools.Field(typeof(FactoryModel), nameof(FactoryModel.whiteMode0))) ).Labels.Add(label2); return matcher.InstructionEnumeration(); } } private class UnlimitInteractive : PatchImpl { [HarmonyTranspiler] [HarmonyPatch(typeof(PlayerAction_Inspect), nameof(PlayerAction_Inspect.GetObjectSelectDistance))] private static IEnumerable PlayerAction_Inspect_GetObjectSelectDistance_Transpiler(IEnumerable instructions) { yield return new CodeInstruction(OpCodes.Ldc_R4, 10000f); yield return new CodeInstruction(OpCodes.Ret); } } private class RemoveSomeConditionBuild : PatchImpl { [HarmonyTranspiler, HarmonyPriority(Priority.First)] [HarmonyPatch(typeof(BuildTool_BlueprintPaste), nameof(BuildTool_BlueprintPaste.CheckBuildConditions))] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click.CheckBuildConditions))] private static IEnumerable BuildTool_Click_CheckBuildConditions_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); /* search for: * ldloc.s V_8 (8) * ldfld class PrefabDesc BuildPreview::desc * ldfld bool PrefabDesc::isInserter * brtrue 2358 (1C12) ldloc.s V_8 (8) * ldloca.s V_10 (10) * call instance float32 [UnityEngine.CoreModule]UnityEngine.Vector3::get_magnitude() */ matcher.MatchForward(false, new CodeMatch(OpCodes.Ldloc_S), new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(BuildPreview), nameof(BuildPreview.desc))), new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(PrefabDesc), nameof(PrefabDesc.isInserter))), new CodeMatch(ci => ci.Branches(out _)), new CodeMatch(OpCodes.Ldloca_S), new CodeMatch(OpCodes.Call, AccessTools.PropertyGetter(typeof(Vector3), nameof(Vector3.magnitude))) ); /* Change to: * Ldloc.s V_8 (8) * ldfld class PrefabDesc BuildPreview::desc * ldfld bool PrefabDesc::isEjector * brfalse 2358 (1C12) ldloc.s V_8 (8) */ matcher.Advance(2); matcher.Operand = AccessTools.Field(typeof(PrefabDesc), nameof(PrefabDesc.isEjector)); matcher.Advance(1); matcher.Opcode = OpCodes.Brfalse; return matcher.InstructionEnumeration(); } [HarmonyTranspiler, HarmonyPriority(Priority.First)] [HarmonyPatch(typeof(BuildTool_Path), nameof(BuildTool_Path.CheckBuildConditions))] private static IEnumerable BuildTool_Path_CheckBuildConditions_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); /* search for: * ldloc.s V_88 (88) * ldloc.s V_120 (120) * brtrue.s 2054 (173A) ldc.i4.s 17 * ldc.i4.s EBuildCondition.JointCannotLift (19) * br.s 2055 (173C) stfld valuetype EBuildCondition BuildPreview::condition * ldc.i4.s EBuildCondition.TooBendToLift (18) * stfld valuetype EBuildCondition BuildPreview::condition */ matcher.MatchForward(false, new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(ci => ci.Branches(out _)), new CodeMatch(ci => ci.opcode == OpCodes.Ldc_I4_S && ci.OperandIs((int)EBuildCondition.JointCannotLift)), new CodeMatch(ci => ci.Branches(out _)), new CodeMatch(ci => ci.opcode == OpCodes.Ldc_I4_S && ci.OperandIs((int)EBuildCondition.TooBendToLift)), new CodeMatch(OpCodes.Stfld, AccessTools.Field(typeof(BuildPreview), nameof(BuildPreview.condition))) ); if (matcher.IsValid) { // Remove 7 instructions, if the following instruction is br/br.s, remove it as well var labels = matcher.Labels; matcher.Labels = []; matcher.RemoveInstructions(7); var opcode = matcher.Opcode; if (opcode == OpCodes.Br || opcode == OpCodes.Br_S) matcher.RemoveInstruction(); matcher.Labels.AddRange(labels); } /* search for: * ldloc.s V_88 (88) * ldc.i4.s EBuildCondition.TooSteep(16)-EBuildCondition.InputConflict(20) * stfld valuetype EBuildCondition BuildPreview::condition */ matcher.Start().MatchForward(false, new CodeMatch(instr => instr.opcode == OpCodes.Ldloc_S || instr.opcode == OpCodes.Ldloc), new CodeMatch(instr => (instr.opcode == OpCodes.Ldc_I4_S || instr.opcode == OpCodes.Ldc_I4) && Convert.ToInt64(instr.operand) is >= (int)EBuildCondition.TooSteep and <= (int)EBuildCondition.InputConflict), new CodeMatch(OpCodes.Stfld, AccessTools.Field(typeof(BuildPreview), nameof(BuildPreview.condition))) ); if (matcher.IsValid) { // Remove 3 instructions, if the following instruction is br/br.s, remove it as well matcher.Repeat(codeMatcher => { var labels = codeMatcher.Labels; codeMatcher.Labels = []; codeMatcher.RemoveInstructions(3); var opcode = codeMatcher.Opcode; if (opcode == OpCodes.Br || opcode == OpCodes.Br_S) codeMatcher.RemoveInstruction(); codeMatcher.Labels.AddRange(labels); }); } return matcher.InstructionEnumeration(); } } private class RemoveBuildRangeLimit : PatchImpl { protected override void OnEnable() { var controller = GameMain.mainPlayer?.controller; if (controller == null) return; controller.actionBuild?.clickTool?._OnInit(); } [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click._OnInit))] private static IEnumerable BuildTool_Click__OnInit_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(ci => ci.opcode == OpCodes.Ldc_I4_S && ci.OperandIs(15)) ); matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4, 512)); return matcher.InstructionEnumeration(); } [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, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); /* Patch (player.mecha.buildArea * player.mecha.buildArea) to 100000000 */ matcher.MatchForward(false, new CodeMatch(), new CodeMatch(OpCodes.Call, AccessTools.PropertyGetter(typeof(BuildTool), nameof(BuildTool.player))), new CodeMatch(OpCodes.Callvirt, AccessTools.PropertyGetter(typeof(Player), nameof(Player.mecha))), new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(Mecha), nameof(Mecha.buildArea))), new CodeMatch(), new CodeMatch(OpCodes.Call, AccessTools.PropertyGetter(typeof(BuildTool), nameof(BuildTool.player))), new CodeMatch(OpCodes.Callvirt, AccessTools.PropertyGetter(typeof(Player), nameof(Player.mecha))), new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(Mecha), nameof(Mecha.buildArea))), new CodeMatch(OpCodes.Mul) ); matcher.Repeat(m => m.RemoveInstructions(9).InsertAndAdvance(new CodeInstruction(OpCodes.Ldc_R4, 100000000.0f))); return matcher.InstructionEnumeration(); } } private class LargerAreaForUpgradeAndDismantle : PatchImpl { [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, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(ci => ci.opcode == OpCodes.Ldc_I4_S && ci.OperandIs(11)) ); matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4_S, 31)); return matcher.InstructionEnumeration(); } } private class LargerAreaForTerraform : PatchImpl { [HarmonyTranspiler, HarmonyPatch(typeof(BuildTool_Reform), nameof(BuildTool_Reform.ReformAction))] private static IEnumerable BuildTool_Reform_ReformAction_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(BuildTool_Reform), nameof(BuildTool_Reform.brushSize))), new CodeMatch(ci => ci.opcode == OpCodes.Ldc_I4_S && ci.OperandIs(10)) ); matcher.Repeat(m => m.Advance(1).SetAndAdvance(OpCodes.Ldc_I4_S, 30)); matcher.Start().MatchForward(false, new CodeMatch(ci => ci.opcode == OpCodes.Ldc_I4_S && ci.OperandIs(10)), new CodeMatch(OpCodes.Stfld, AccessTools.Field(typeof(BuildTool_Reform), nameof(BuildTool_Reform.brushSize))) ); matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4_S, 30)); return matcher.InstructionEnumeration(); } } public class OffGridBuilding : PatchImpl { // private const float SteppedRotationDegrees = 15f; private static bool _initialized; private static void SetupRichTextSupport() { if (_initialized) return; UIGeneralTips.instance.buildCursorTextComp.supportRichText = true; UIGeneralTips.instance.entityBriefInfo.entityNameText.supportRichText = true; _initialized = true; } private static void CalculateGridOffset(PlanetData planet, Vector3 pos, out float x, out float y, out float z) { var npos = pos.normalized; var segment = planet.aux.activeGrid?.segment ?? 200; var latitudeRadPerGrid = BlueprintUtils.GetLatitudeRadPerGrid(segment); var longitudeSegmentCount = BlueprintUtils.GetLongitudeSegmentCount(npos, segment); var longitudeRadPerGrid = BlueprintUtils.GetLongitudeRadPerGrid(longitudeSegmentCount, segment); var latitudeRad = BlueprintUtils.GetLatitudeRad(npos); var longitudeRad = BlueprintUtils.GetLongitudeRad(npos); x = longitudeRad / longitudeRadPerGrid; y = latitudeRad / latitudeRadPerGrid; z = (pos.magnitude - planet.realRadius - 0.2f) / 1.3333333f; } private static string FormatOffsetFloat(float f) { return f.ToString("0.0000").TrimEnd('0').TrimEnd('.'); } private static PlanetData _lastPlanet; private static Vector3 _lastPos; private static string _lastOffsetText; [HarmonyPostfix] [HarmonyPriority(Priority.Last)] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click.CheckBuildConditions))] [HarmonyPatch(typeof(BuildTool_Path), nameof(BuildTool_Path.CheckBuildConditions))] private static void BuildTool_Click_CheckBuildConditions_Postfix(BuildTool __instance) { var cnt = __instance.buildPreviews.Count; if (cnt == 0) return; var preview = __instance.buildPreviews[cnt - 1]; if (preview.desc.isInserter) return; var planet = __instance.planet; if (_lastPlanet != planet || _lastPos != preview.lpos) { SetupRichTextSupport(); CalculateGridOffset(__instance.planet, preview.lpos, out var x, out var y, out var z); _lastPlanet = planet; _lastPos = preview.lpos; _lastOffsetText = z is < 0.001f and > -0.001f ? $"{FormatOffsetFloat(x)},{FormatOffsetFloat(y)}" : $"{FormatOffsetFloat(x)},{FormatOffsetFloat(y)},{FormatOffsetFloat(z)}"; } __instance.actionBuild.model.cursorText = $"({_lastOffsetText})\n" + __instance.actionBuild.model.cursorText; } [HarmonyTranspiler] [HarmonyPatch(typeof(UIEntityBriefInfo), nameof(UIEntityBriefInfo._OnUpdate))] private static IEnumerable UIEntityBriefInfo__OnUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(UIEntityBriefInfo), nameof(UIEntityBriefInfo.entityNameText))), new CodeMatch(OpCodes.Callvirt, AccessTools.PropertyGetter(typeof(Text), nameof(Text.preferredWidth))) ); matcher.InsertAndAdvance( new CodeInstruction(OpCodes.Ldarg_0), Transpilers.EmitDelegate((UIEntityBriefInfo entityBriefInfo) => { var entity = entityBriefInfo.factory.entityPool[entityBriefInfo.entityId]; if (entity.inserterId > 0) return; var planet = entityBriefInfo.factory.planet; if (_lastPlanet != planet || _lastPos != entity.pos) { SetupRichTextSupport(); CalculateGridOffset(planet, entity.pos, out var x, out var y, out var z); _lastPlanet = planet; _lastPos = entity.pos; _lastOffsetText = $"{FormatOffsetFloat(x)},{FormatOffsetFloat(y)},{FormatOffsetFloat(z)}"; } entityBriefInfo.entityNameText.text += $" ({_lastOffsetText})"; } ) ); return matcher.InstructionEnumeration(); } private static void MatchIgnoreGridAndCheckIfRotatable(CodeMatcher matcher, out Label? ifBlockEntryLabel, out Label? elseBlockEntryLabel) { Label? thisIfBlockEntryLabel = null; Label? thisElseBlockEntryLabel = null; matcher.MatchForward(false, new CodeMatch(ci => ci.Calls(AccessTools.PropertyGetter(typeof(VFInput), nameof(VFInput._switchGridSnap)))), new CodeMatch(ci => ci.Branches(out thisElseBlockEntryLabel)), new CodeMatch(ci => ci.IsLdarg()), new CodeMatch(OpCodes.Ldfld), new CodeMatch(OpCodes.Ldfld), new CodeMatch(ci => ci.LoadsConstant(EMinerType.Vein)), new CodeMatch(ci => ci.Branches(out thisIfBlockEntryLabel)), new CodeMatch(ci => ci.IsLdarg()), new CodeMatch(OpCodes.Ldfld), new CodeMatch(OpCodes.Ldfld) ); ifBlockEntryLabel = thisIfBlockEntryLabel; elseBlockEntryLabel = thisElseBlockEntryLabel; } [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click.UpdateRaycast))] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click.DeterminePreviews))] public static IEnumerable AllowOffGridConstruction(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); MatchIgnoreGridAndCheckIfRotatable(matcher, out var entryLabel, out _); if (matcher.IsInvalid) return instructions; matcher.Advance(2); matcher.Insert(new CodeInstruction(OpCodes.Br, entryLabel.Value)); return matcher.InstructionEnumeration(); } [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click.DeterminePreviews))] public static IEnumerable PreventDraggingWhenOffGrid(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); Label? exitLabel = null; matcher.MatchForward(false, new CodeMatch(ci => ci.Branches(out exitLabel)), new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(ci => ci.LoadsConstant(1)), new CodeMatch(ci => ci.StoresField(AccessTools.Field(typeof(BuildTool_Click), nameof(BuildTool_Click.isDragging)))) ); if (matcher.IsInvalid) return instructions; matcher.Advance(1); matcher.Insert( new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(VFInput), nameof(VFInput._switchGridSnap))), new CodeInstruction(OpCodes.Brtrue, exitLabel) ); return matcher.InstructionEnumeration(); } [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Path), nameof(BuildTool_Path.UpdateRaycast))] public static IEnumerable AllowOffGridConstructionForPath(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(OpCodes.Call, AccessTools.PropertyGetter(typeof(BuildTool), nameof(BuildTool.actionBuild))), new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(PlayerAction_Build), nameof(PlayerAction_Build.planetAux))), new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(BuildTool_Path), nameof(BuildTool_Path.castGroundPos))), new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(BuildTool_Path), nameof(BuildTool_Path.castTerrain))), new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(PlanetAuxData), nameof(PlanetAuxData.Snap), [typeof(Vector3), typeof(bool)])), new CodeMatch(OpCodes.Stfld, AccessTools.Field(typeof(BuildTool_Path), nameof(BuildTool_Path.castGroundPosSnapped))) ); if (matcher.IsInvalid) return matcher.InstructionEnumeration(); var jmp0 = generator.DefineLabel(); var jmp1 = generator.DefineLabel(); matcher.InsertAndAdvance( new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(FactoryPatch), nameof(_offgridfForPathsKey))), new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(KeyBindings), nameof(KeyBindings.IsKeyPressing))), new CodeInstruction(OpCodes.Brfalse, jmp0), new CodeInstruction(OpCodes.Ldarg_0), new CodeInstruction(OpCodes.Ldarg_0), new CodeInstruction(OpCodes.Ldflda, AccessTools.Field(typeof(BuildTool_Path), nameof(BuildTool_Path.castGroundPos))), new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(Vector3), nameof(Vector3.normalized))), new CodeInstruction(OpCodes.Ldarg_0), new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(BuildTool_Path), nameof(BuildTool_Path.planet))), new CodeInstruction(OpCodes.Callvirt, AccessTools.PropertyGetter(typeof(PlanetData), nameof(PlanetData.realRadius))), new CodeInstruction(OpCodes.Ldc_R4, 0.2f), new CodeInstruction(OpCodes.Add), new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Vector3), "op_Multiply", [typeof(Vector3), typeof(float)])), new CodeInstruction(OpCodes.Stfld, AccessTools.Field(typeof(BuildTool_Path), nameof(BuildTool_Path.castGroundPosSnapped))), new CodeInstruction(OpCodes.Br, jmp1) ).Labels.Add(jmp0); matcher.Advance(10).Labels.Add(jmp1); return matcher.InstructionEnumeration(); } /* public static IEnumerable PatchToPerformSteppedRotate(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); MatchIgnoreGridAndCheckIfRotatable(matcher, out var ifBlockEntryLabel, out var elseBlockEntryLabel); if (matcher.IsInvalid) return instructions; while (!matcher.Labels.Contains(elseBlockEntryLabel.Value)) matcher.Advance(1); Label? ifBlockExitLabel = null; matcher.MatchBack(false, new CodeMatch(ci => ci.Branches(out ifBlockExitLabel))); if (matcher.IsInvalid) return instructions; while (!matcher.Labels.Contains(ifBlockEntryLabel.Value)) matcher.Advance(-1); var instructionToClone = matcher.Instruction.Clone(); var overwriteWith = CodeInstruction.LoadField(typeof(VFInput), nameof(VFInput.control)); matcher.SetAndAdvance(overwriteWith.opcode, overwriteWith.operand); matcher.Insert(instructionToClone); matcher.CreateLabel(out var existingEntryLabel); matcher.InsertAndAdvance( new CodeInstruction(OpCodes.Brfalse, existingEntryLabel), new CodeInstruction(OpCodes.Ldarg_0), CodeInstruction.Call(typeof(OffGridBuilding), nameof(RotateStepped)), new CodeInstruction(OpCodes.Br, ifBlockExitLabel) ); return matcher.InstructionEnumeration(); } public static void RotateStepped(BuildTool_Click instance) { if (VFInput._rotate.onDown) { instance.yaw += SteppedRotationDegrees; instance.yaw = Mathf.Repeat(instance.yaw, 360f); instance.yaw = Mathf.Round(instance.yaw / SteppedRotationDegrees) * SteppedRotationDegrees; } if (VFInput._counterRotate.onDown) { instance.yaw -= SteppedRotationDegrees; instance.yaw = Mathf.Repeat(instance.yaw, 360f); instance.yaw = Mathf.Round(instance.yaw / SteppedRotationDegrees) * SteppedRotationDegrees; } } */ } public class TreatStackingAsSingle : PatchImpl { [HarmonyTranspiler] [HarmonyPatch(typeof(MonitorComponent), nameof(MonitorComponent.InternalUpdate))] private static IEnumerable MonitorComponent_InternalUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Call, AccessTools.Method(typeof(MonitorComponent), nameof(MonitorComponent.GetCargoAtIndexByFilter))) ); matcher.Advance(-3); var localVar = matcher.Operand; matcher.Advance(4).Insert( new CodeInstruction(OpCodes.Ldloca, localVar), new CodeInstruction(OpCodes.Ldc_I4_1), new CodeInstruction(OpCodes.Stfld, AccessTools.Field(typeof(Cargo), nameof(Cargo.stack))) ); return matcher.InstructionEnumeration(); } } private class QuickBuildAndDismantleLab : PatchImpl { private static bool DetermineMoreLabsForDismantle(BuildTool dismantle, int id) { if (!VFInput._chainReaction) return true; var factory = dismantle.factory; var proto = dismantle.GetItemProto(id); var protoId = proto.ID; var prefDesc = proto.prefabDesc; if (!prefDesc.isLab && !prefDesc.isTank && (!prefDesc.isStorage || prefDesc.isBattleBase)) return true; factory.ReadObjectConn(id, 14, out _, out var nextId, out _); /* We keep last lab if selected lab is not the ground one */ if (nextId > 0) { while (true) { factory.ReadObjectConn(nextId, 14, out _, out var nextNextId, out _); if (nextNextId <= 0) break; var itemProto = dismantle.GetItemProto(nextId); if (itemProto.ID != protoId) break; var desc = itemProto.prefabDesc; var pose = dismantle.GetObjectPose(nextId); var preview = new BuildPreview { item = itemProto, desc = desc, lpos = pose.position, lrot = pose.rotation, lpos2 = pose.position, lrot2 = pose.rotation, objId = nextId, needModel = desc.lodCount > 0 && desc.lodMeshes[0] != null, isConnNode = true }; dismantle.buildPreviews.Add(preview); nextId = nextNextId; } } nextId = id; while (true) { factory.ReadObjectConn(nextId, 15, out _, out var nextId2, out _); if (nextId2 <= 0) { factory.ReadObjectConn(nextId, 13, out _, out nextId2, out _); if (nextId2 <= 0) break; } nextId = nextId2; var itemProto = dismantle.GetItemProto(nextId); var desc = itemProto.prefabDesc; var pose = dismantle.GetObjectPose(nextId); var preview = new BuildPreview { item = itemProto, desc = desc, lpos = pose.position, lrot = pose.rotation, lpos2 = pose.position, lrot2 = pose.rotation, objId = nextId, needModel = desc.lodCount > 0 && desc.lodMeshes[0] != null, isConnNode = true }; dismantle.buildPreviews.Add(preview); } return false; } private static void BuildLabsToTop(BuildTool_Click click) { if (!click.multiLevelCovering || !VFInput._chainReaction) return; var prefDesc = click.GetPrefabDesc(click.castObjectId); if (!prefDesc.isLab && !prefDesc.isTank && (!prefDesc.isStorage || prefDesc.isBattleBase)) return; var levelMax = prefDesc.isLab ? GameMain.history.labLevel : GameMain.history.storageLevel; var factory = click.factory; var currLevel = 2; var nid = click.castObjectId; do { factory.ReadObjectConn(nid, 14, out _, out nid, out _); if (nid <= 0) break; currLevel++; } while (true); while (currLevel < levelMax) { click.UpdateRaycast(); click.DeterminePreviews(); click.UpdateCollidersForCursor(); click.UpdateCollidersForGiantBp(); var model = click.actionBuild.model; click.UpdatePreviewModels(model); if (!click.CheckBuildConditions()) { model.ClearAllPreviewsModels(); model.EarlyGameTickIgnoreActive(); return; } click.UpdatePreviewModelConditions(model); click.UpdateGizmos(model); click.CreatePrebuilds(); currLevel++; } } [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Dismantle), nameof(BuildTool_Dismantle.DeterminePreviews))] private static IEnumerable BuildTool_Dismantle_DeterminePreviews_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldloc_3), new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(BuildPreview), nameof(BuildPreview.desc))), new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(PrefabDesc), nameof(PrefabDesc.isBattleBase))), new CodeMatch(OpCodes.Brfalse) ).Advance(-1); matcher.InsertAndAdvance( new CodeInstruction(OpCodes.Ldarg_0), new CodeInstruction(OpCodes.Ldloc_3), new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(BuildPreview), nameof(BuildPreview.objId))), new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(QuickBuildAndDismantleLab), nameof(DetermineMoreLabsForDismantle))), new CodeInstruction(OpCodes.And) ); return matcher.InstructionEnumeration(); } [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click._OnTick))] private static IEnumerable BuildTool_Click__OnTick_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(OpCodes.Call, AccessTools.Method(typeof(BuildTool_Click), nameof(BuildTool_Click.CreatePrebuilds))) ).Advance(2); matcher.InsertAndAdvance( new CodeInstruction(OpCodes.Ldarg_0), new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(QuickBuildAndDismantleLab), nameof(BuildLabsToTop))) ); return matcher.InstructionEnumeration(); } } public class ProtectVeinsFromExhaustion : PatchImpl { public static int KeepVeinAmount = 100; public static float KeepOilSpeed = 1f; private static int _keepOilAmount; public static void InitConfig() { _keepOilAmount = Math.Max((int)(KeepOilSpeed / 0.00004f + 0.5f), 2500); } [HarmonyPrefix] [HarmonyPatch(typeof(MinerComponent), nameof(MinerComponent.InternalUpdate))] private static bool MinerComponent_InternalUpdate_Prefix(PlanetFactory factory, VeinData[] veinPool, float power, float miningRate, float miningSpeed, int[] productRegister, ref MinerComponent __instance, out uint __result) { if (power < 0.1f) { __result = 0U; return false; } var res = 0U; int veinId; int times; switch (__instance.type) { case EMinerType.Vein: var veinCount = __instance.veinCount; if (veinCount <= 0) break; if (__instance.time <= __instance.period) { __instance.time += (int)(power * __instance.speedDamper * __instance.speed * miningSpeed * veinCount); res = 1U; } if (__instance.time < __instance.period) { break; } var currentVeinIndex = __instance.currentVeinIndex; veinId = __instance.veins[currentVeinIndex]; lock (veinPool) { if (veinPool[veinId].id == 0) { __instance.RemoveVeinFromArray(currentVeinIndex); __instance.GetMinimumVeinAmount(factory, veinPool); veinCount = __instance.veinCount; __instance.currentVeinIndex = veinCount > 1 ? currentVeinIndex % veinCount : 0; __result = 0U; return false; } if (__instance.productCount < 50 && (__instance.productId == 0 || __instance.productId == veinPool[veinId].productId)) { __instance.productId = veinPool[veinId].productId; times = __instance.time / __instance.period; var outputCount = 0; var amount = veinPool[veinId].amount; if (miningRate > 0f) { if (amount > KeepVeinAmount) { var usedCount = 0; var maxAllowed = amount - KeepVeinAmount; var add = miningRate * (double)times; __instance.costFrac += add; var estimateUses = (int)__instance.costFrac; if (estimateUses < maxAllowed) { outputCount = times; usedCount = estimateUses; __instance.costFrac -= estimateUses; } else { usedCount = maxAllowed; var oldFrac = __instance.costFrac - add; var ratio = (usedCount - oldFrac) / add; var realCost = times * ratio; outputCount = (int)(Math.Ceiling(realCost) + 0.01); __instance.costFrac = miningRate * (outputCount - realCost); } if (usedCount > 0) { var groupIndex = (int)veinPool[veinId].groupIndex; amount -= usedCount; veinPool[veinId].amount = amount; if (amount < __instance.minimumVeinAmount) { __instance.minimumVeinAmount = amount; } factory.veinGroups[groupIndex].amount -= usedCount; factory.veinAnimPool[veinId].time = amount >= 20000 ? 0f : 1f - 0.00005f; if (amount <= 0) { var venType = (int)veinPool[veinId].type; var pos = veinPool[veinId].pos; factory.RemoveVeinWithComponents(veinId); factory.RecalculateVeinGroup(groupIndex); factory.NotifyVeinExhausted(venType, groupIndex, pos); veinCount = __instance.veinCount; } else { currentVeinIndex++; } } } else { if (amount <= 0) { __instance.RemoveVeinFromArray(currentVeinIndex); __instance.GetMinimumVeinAmount(factory, veinPool); veinCount = __instance.veinCount; } else { currentVeinIndex++; } __instance.currentVeinIndex = veinCount > 1 ? currentVeinIndex % veinCount : 0; break; } } else { outputCount = times; } __instance.productCount += outputCount; lock (productRegister) { productRegister[__instance.productId] += outputCount; factory.AddMiningFlagUnsafe(veinPool[veinId].type); factory.AddVeinMiningFlagUnsafe(veinPool[veinId].type); } __instance.time -= __instance.period * outputCount; __instance.currentVeinIndex = veinCount > 1 ? currentVeinIndex % veinCount : 0; } } break; case EMinerType.Oil: if (__instance.veinCount <= 0) break; veinId = __instance.veins[0]; lock (veinPool) { var amount = veinPool[veinId].amount; var workCount = amount * VeinData.oilSpeedMultiplier; if (__instance.time < __instance.period) { __instance.time += (int)(power * __instance.speedDamper * __instance.speed * miningSpeed * workCount + 0.5f); res = 1U; } if (__instance.time >= __instance.period && __instance.productCount < 50) { __instance.productId = veinPool[veinId].productId; times = __instance.time / __instance.period; if (times <= 0) break; var outputCount = 0; if (miningRate > 0f) { if (amount > _keepOilAmount) { var usedCount = 0; var maxAllowed = amount - _keepOilAmount; var add = miningRate * (double)times; __instance.costFrac += add; var estimateUses = (int)__instance.costFrac; if (estimateUses < maxAllowed) { outputCount = times; usedCount = estimateUses; __instance.costFrac -= estimateUses; } else { usedCount = maxAllowed; var oldFrac = __instance.costFrac - add; var ratio = (usedCount - oldFrac) / add; var realCost = times * ratio; outputCount = (int)(Math.Ceiling(realCost) + 0.01); __instance.costFrac = miningRate * (outputCount - realCost); } if (usedCount > 0) { if (usedCount > maxAllowed) { usedCount = maxAllowed; } amount -= usedCount; veinPool[veinId].amount = amount; var groupIndex = veinPool[veinId].groupIndex; factory.veinGroups[groupIndex].amount -= usedCount; factory.veinAnimPool[veinId].time = amount >= 25000 ? 0f : 1f - amount * VeinData.oilSpeedMultiplier; if (amount <= 2500) { factory.NotifyVeinExhausted((int)veinPool[veinId].type, groupIndex, veinPool[veinId].pos); } } } else if (_keepOilAmount <= 2500) { outputCount = times; } else { break; } } else { outputCount = times; } __instance.productCount += outputCount; lock (productRegister) { productRegister[__instance.productId] += outputCount; } __instance.time -= __instance.period * outputCount; } } break; case EMinerType.Water: if (__instance.time < __instance.period) { __instance.time += (int)(power * __instance.speedDamper * __instance.speed * miningSpeed); res = 1U; } if (__instance.time < __instance.period) break; times = __instance.time / __instance.period; if (__instance.productCount >= 50) break; __instance.productId = factory.planet.waterItemId; do { if (__instance.productId > 0) { __instance.productCount += times; lock (productRegister) { productRegister[__instance.productId] += times; break; } } __instance.productId = 0; } while (false); __instance.time -= __instance.period * times; break; } if (__instance is { productCount: > 0, insertTarget: > 0, productId: > 0 }) { var multiplier = 36000000.0 / __instance.period * miningSpeed; if (__instance.type == EMinerType.Vein) { multiplier *= __instance.veinCount; } else if (__instance.type == EMinerType.Oil) { multiplier *= veinPool[__instance.veins[0]].amount * VeinData.oilSpeedMultiplier; } var count = (int)(multiplier - 0.01) / 1800 + 1; count = count < 4 ? count < 1 ? 1 : count : 4; var stack = __instance.productCount < count ? __instance.productCount : count; var outputCount = factory.InsertInto(__instance.insertTarget, 0, __instance.productId, (byte)stack, 0, out _); __instance.productCount -= outputCount; if (__instance is { productCount: 0, type: EMinerType.Vein }) { __instance.productId = 0; } } __result = res; return false; } } private class DoNotRenderEntities : PatchImpl { [HarmonyPrefix] [HarmonyPatch(typeof(ObjectRenderer), nameof(ObjectRenderer.Render))] [HarmonyPatch(typeof(DynamicRenderer), nameof(DynamicRenderer.Render))] private static bool ObjectRenderer_Render_Prefix() { return false; } [HarmonyPrefix] [HarmonyPatch(typeof(LabRenderer), nameof(LabRenderer.Render))] private static bool LabRenderer_Render_Prefix() { return false; } [HarmonyPostfix] [HarmonyPatch(typeof(GPUInstancingManager), nameof(GPUInstancingManager.Render))] private static void FactoryModel_DrawInstancedBatches_Postfix(GPUInstancingManager __instance) { __instance.renderEntity = true; } [HarmonyTranspiler] [HarmonyPatch(typeof(RaycastLogic), nameof(RaycastLogic.GameTick))] private static IEnumerable RaycastLogic_GameTick_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldc_I4_0), new CodeMatch(OpCodes.Brtrue)); var branch1 = (Label)matcher.Advance(1).Operand; var branch2 = generator.DefineLabel(); matcher.Start().MatchForward(false, new CodeMatch(OpCodes.Call), new CodeMatch(ci => ci.IsStloc()), new CodeMatch(OpCodes.Call), new CodeMatch(ci => ci.IsStloc()), new CodeMatch(OpCodes.Call), new CodeMatch(ci => ci.IsStloc()), new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(ci => ci.Branches(out _)), new CodeMatch(OpCodes.Ldarg_0) ).Advance(8).InsertAndAdvance( new CodeInstruction(OpCodes.Ldloc_S, 45), new CodeInstruction(OpCodes.Brtrue, branch2), new CodeInstruction(OpCodes.Ldarg_0), new CodeInstruction(OpCodes.Ldloc_S, 33), new CodeInstruction(OpCodes.Ldloc_S, 35), Transpilers.EmitDelegate((RaycastLogic l, ColliderData[] colliderPool, int j) => l.factory.entityPool[colliderPool[j].objId].inserterId > 0), new CodeInstruction(OpCodes.Brfalse, branch1) ); matcher.Labels.Add(branch2); return matcher.InstructionEnumeration(); } } private class DragBuildPowerPoles : PatchImpl { private static readonly List OldDragBuild = []; private static readonly List OldDragBuildDist = []; private static readonly int[] PowerPoleIds = [2202, 2212]; protected override void OnEnable() { GameLogicProc.OnGameBegin += GameMain_Begin_Postfix; GameLogicProc.OnGameEnd += GameMain_End_Postfix; FixProto(); } protected override void OnDisable() { UnfixProto(); GameLogicProc.OnGameEnd -= GameMain_End_Postfix; GameLogicProc.OnGameBegin -= GameMain_Begin_Postfix; } public static void AlternatelyChanged() { UnfixProto(); FixProto(); } private static bool IsPowerPole(int id) { return PowerPoleIds.Contains(id); } private static void FixProto() { if (DSPGame.IsMenuDemo) return; OldDragBuild.Clear(); OldDragBuildDist.Clear(); foreach (var id in PowerPoleIds) { var prefabDesc = LDB.items.Select(id)?.prefabDesc; if (prefabDesc == null) return; OldDragBuild.Add(prefabDesc.dragBuild); OldDragBuildDist.Add(prefabDesc.dragBuildDist); prefabDesc.dragBuild = true; var distance = prefabDesc.powerConnectDistance - 0.72f; prefabDesc.dragBuildDist = new Vector2(distance, distance); } } private static void UnfixProto() { if (GetHarmony() == null || OldDragBuild.Count < 3 || DSPGame.IsMenuDemo) return; var i = 0; foreach (var id in PowerPoleIds) { var powerPole = LDB.items.Select(id); if (powerPole?.prefabDesc != null) { powerPole.prefabDesc.dragBuild = OldDragBuild[i]; powerPole.prefabDesc.dragBuildDist = OldDragBuildDist[i]; } i++; } OldDragBuild.Clear(); OldDragBuildDist.Clear(); } private static void GameMain_Begin_Postfix() { FixProto(); } private static void GameMain_End_Postfix() { UnfixProto(); } private static int PlanetGridSnapDotsNonAllocNotAligned(PlanetGrid planetGrid, Vector3 begin, Vector3 end, Vector2 interval, float yaw, float planetRadius, float gap, Vector3[] snaps) { begin = begin.normalized; end = end.normalized; var finalCount = 1; var ignoreGrid = VFInput._switchGridSnap; if (ignoreGrid) snaps[0] = begin; else snaps[0] = planetGrid.SnapTo(begin); var dot = Vector3.Dot(begin, end); if (dot is > 0.999999f or < -0.999999f) return 1; var distTotal = Mathf.Acos(dot) * planetRadius; var intervalAll = interval.x; var maxT = 1f - intervalAll * 0.5f / distTotal; if (maxT < 0f) return 1; var maxCount = snaps.Length; while (finalCount < maxCount) { var t = finalCount * intervalAll / distTotal; if (ignoreGrid) snaps[finalCount] = Vector3.Slerp(begin, end, t); else snaps[finalCount] = planetGrid.SnapTo(Vector3.Slerp(begin, end, t)); finalCount++; if (t > maxT) break; } return finalCount; } private static int PlanetAuxDataSnapDotsNonAllocNotAligned(PlanetAuxData aux, Vector3 begin, Vector3 end, Vector2 interval, float height, float yaw, float gap, Vector3[] snaps) { var num = 0; var magnitude = begin.magnitude; if (aux.activeGrid != null) { num = PlanetGridSnapDotsNonAllocNotAligned(aux.activeGrid, begin, end, interval, yaw, aux.planet.realRadius + height, gap, snaps); for (var i = 0; i < num; i++) { snaps[i] *= magnitude; } } else { snaps[num++] = aux.Snap(begin, false); } return num; } [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click.DeterminePreviews))] private static IEnumerable BuildTool_Click_DeterminePreviews_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); var label1 = generator.DefineLabel(); var label2 = generator.DefineLabel(); matcher.MatchForward(false, new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(PlanetAuxData), nameof(PlanetAuxData.SnapDotsNonAlloc))) ); matcher.Labels.Add(label1); matcher.InstructionAt(1).labels.Add(label2); matcher.InsertAndAdvance( new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(VFInput), nameof(VFInput.control))), new CodeInstruction(OpCodes.Brtrue, label1), new CodeInstruction(OpCodes.Ldarg_0), new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(BuildTool_Click), nameof(BuildTool_Click.handItem))), new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(ItemProto), nameof(ItemProto.ID))), new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(DragBuildPowerPoles), nameof(IsPowerPole))), new CodeInstruction(OpCodes.Brfalse, label1), new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(DragBuildPowerPoles), nameof(PlanetAuxDataSnapDotsNonAllocNotAligned))), new CodeInstruction(OpCodes.Br, label2) ).Advance(1).MatchForward(false, new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(BuildTool_Click), nameof(BuildTool_Click.handItem))), new CodeMatch(OpCodes.Stfld, AccessTools.Field(typeof(BuildPreview), nameof(BuildPreview.item))), new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(BuildTool_Click), nameof(BuildTool_Click.handPrefabDesc))), new CodeMatch(OpCodes.Stfld, AccessTools.Field(typeof(BuildPreview), nameof(BuildPreview.desc))) ); var pos = matcher.Pos; matcher.MatchBack(false, new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(OpCodes.Mul), new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(OpCodes.Add) ); var operand = matcher.Operand; matcher.Start().Advance(pos); matcher.Advance(2).InsertAndAdvance(new CodeInstruction(OpCodes.Ldloc_S, operand)).SetInstructionAndAdvance(Transpilers.EmitDelegate((BuildTool_Click click, int i) => { if (!DragBuildPowerPolesAlternatelyEnabled.Value || (i & 1) == 0) return click.handItem; var id = click.handItem.ID; if (id != 2202) return click.handItem; return LDB.items.Select(id ^ 3); })).Advance(3).InsertAndAdvance(new CodeInstruction(OpCodes.Ldloc_S, operand)).SetInstructionAndAdvance(Transpilers.EmitDelegate((BuildTool_Click click, int i) => { if (!DragBuildPowerPolesAlternatelyEnabled.Value || (i & 1) == 0) return click.handPrefabDesc; var id = click.handItem.ID; if (id != 2202) return click.handPrefabDesc; return LDB.items.Select(id ^ 3).prefabDesc; })); return matcher.InstructionEnumeration(); } } private class BeltSignalsForBuyOut : PatchImpl { private static bool _initialized; private static bool _loaded; private static long _clusterSeedKey; private static readonly int[] DarkFogItemIds = [5201, 5206, 5202, 5204, 5203, 5205]; private static readonly int[] DarkFogItemExchangeRate = [20, 60, 30, 30, 30, 10]; public static readonly int[] DarkFogItemsInVoid = [0, 0, 0, 0, 0, 0]; private static Dictionary[] _signalBelts = new Dictionary[64]; private static readonly HashSet SignalBeltFactoryIndices = []; public static void InitPersist() { Persist.Enable(true); } public static void UninitPersist() { Persist.Enable(false); } private static void AddBeltSignalProtos() { if (!_initialized || _loaded) return; var assembly = Assembly.GetExecutingAssembly(); var signals = LDB._signals; SignalProto[] protos = [ new SignalProto { ID = 301, Name = "存储单元", GridIndex = 3601, IconPath = "assets/signal/memory.png", _iconSprite = Util.LoadEmbeddedSprite("assets/signal/memory.png", assembly), SID = "" }, new SignalProto { ID = 302, Name = "能量碎片", GridIndex = 3602, IconPath = "assets/signal/energy-fragment.png", _iconSprite = Util.LoadEmbeddedSprite("assets/signal/energy-fragment.png", assembly), SID = "" }, new SignalProto { ID = 303, Name = "硅基神经元", GridIndex = 3603, IconPath = "assets/signal/silicon-neuron.png", _iconSprite = Util.LoadEmbeddedSprite("assets/signal/silicon-neuron.png", assembly), SID = "" }, new SignalProto { ID = 304, Name = "负熵奇点", GridIndex = 3604, IconPath = "assets/signal/negentropy.png", _iconSprite = Util.LoadEmbeddedSprite("assets/signal/negentropy.png", assembly), SID = "" }, new SignalProto { ID = 305, Name = "物质重组器", GridIndex = 3605, IconPath = "assets/signal/reassembler.png", _iconSprite = Util.LoadEmbeddedSprite("assets/signal/reassembler.png", assembly), SID = "" }, new SignalProto { ID = 306, Name = "虚粒子", GridIndex = 3606, IconPath = "assets/signal/virtual-particle.png", _iconSprite = Util.LoadEmbeddedSprite("assets/signal/virtual-particle.png", assembly), SID = "" }, ]; foreach (var proto in protos) { proto.name = proto.Name.Translate(); } var index = signals.dataArray.Length; signals.dataArray = signals.dataArray.Concat(protos).ToArray(); foreach (var proto in protos) { signals.dataIndices[proto.ID] = index; index++; } _loaded = true; } private static void RemoveBeltSignalProtos() { if (!_initialized || !_loaded) return; var signals = LDB._signals; if (signals.dataIndices.TryGetValue(301, out var index)) { signals.dataArray = signals.dataArray.Take(index).Concat(signals.dataArray.Skip(index + 6)).ToArray(); for (var id = 301; id <= 306; id++) signals.dataIndices.Remove(id); var len = signals.dataArray.Length; for (; index < len; index++) signals.dataIndices[signals.dataArray[index].ID] = index; } _loaded = false; } private static void InitSignalBelts() { if (!GameMain.isRunning) return; var factories = GameMain.data?.factories; if (factories == null) return; foreach (var factory in factories) { var entitySignPool = factory?.entitySignPool; if (entitySignPool == null) continue; var cargoTraffic = factory.cargoTraffic; var beltPool = cargoTraffic.beltPool; for (var i = cargoTraffic.beltCursor - 1; i > 0; i--) { if (beltPool[i].id != i) continue; ref var signal = ref entitySignPool[beltPool[i].entityId]; var signalId = signal.iconId0; if (signalId is < 301U or > 306U) continue; SetSignalBelt(factory.index, i, signalId - 301U); } } } private static void SetSignalBelt(int factory, int beltId, uint signal) { var signalBelts = GetOrCreateSignalBelts(factory); if (signalBelts.Count == 0) SignalBeltFactoryIndices.Add(factory); signalBelts[beltId] = signal; } private static Dictionary GetOrCreateSignalBelts(int index) { Dictionary obj; if (index < 0) return null; if (index >= _signalBelts.Length) { Array.Resize(ref _signalBelts, index * 2); } else { obj = _signalBelts[index]; if (obj != null) return obj; } obj = new Dictionary(); _signalBelts[index] = obj; return obj; } private static Dictionary GetSignalBelts(int index) { return index >= 0 && index < _signalBelts.Length ? _signalBelts[index] : null; } private static void RemoveSignalBelt(int factory, int beltId) { var signalBelts = GetSignalBelts(factory); if (signalBelts == null) return; signalBelts.Remove(beltId); if (signalBelts.Count == 0) SignalBeltFactoryIndices.Remove(factory); } private static void RemovePlanetSignalBelts(int factory) { var signalBelts = GetSignalBelts(factory); if (signalBelts == null) return; signalBelts.Clear(); SignalBeltFactoryIndices.Remove(factory); } private class Persist : PatchImpl { protected override void OnEnable() { AddBeltSignalProtos(); GameLogicProc.OnDataLoaded += VFPreload_InvokeOnLoadWorkEnded_Postfix; GameLogicProc.OnGameBegin += GameMain_Begin_Postfix; } protected override void OnDisable() { GameLogicProc.OnGameBegin -= GameMain_Begin_Postfix; GameLogicProc.OnDataLoaded -= VFPreload_InvokeOnLoadWorkEnded_Postfix; } private static void VFPreload_InvokeOnLoadWorkEnded_Postfix() { if (_initialized) return; _initialized = true; AddBeltSignalProtos(); } [HarmonyPostfix] [HarmonyPatch(typeof(DigitalSystem), MethodType.Constructor, typeof(PlanetData))] private static void DigitalSystem_Constructor_Postfix(PlanetData _planet) { var player = GameMain.mainPlayer; if (player == null) return; var factory = _planet?.factory; if (factory == null) return; RemovePlanetSignalBelts(factory.index); } private static void GameMain_Begin_Postfix() { _clusterSeedKey = GameMain.data.GetClusterSeedKey(); InitSignalBelts(); } [HarmonyPrefix] [HarmonyPatch(typeof(CargoTraffic), nameof(CargoTraffic.RemoveBeltComponent))] public static void CargoTraffic_RemoveBeltComponent_Prefix(int id) { var planet = GameMain.localPlanet; if (planet == null) return; RemoveSignalBelt(planet.factoryIndex, id); } [HarmonyPostfix] [HarmonyPatch(typeof(CargoTraffic), nameof(CargoTraffic.SetBeltSignalIcon))] public static void CargoTraffic_SetBeltSignalIcon_Postfix(CargoTraffic __instance, int entityId, int signalId) { var planet = GameMain.localPlanet; if (planet == null) return; var factory = __instance.factory; var factoryIndex = planet.factoryIndex; var beltId = factory.entityPool[entityId].beltId; if (signalId is < 301 or > 306) { RemoveSignalBelt(factoryIndex, beltId); } else { SetSignalBelt(factoryIndex, beltId, (uint)signalId - 301U); } } } [HarmonyPostfix] [HarmonyPatch(typeof(GameLogic), nameof(GameLogic.OnFactoryFrameBegin))] public static void GameLogic_OnFactoryFrameBegin_Postfix() { var factories = GameMain.data?.factories; if (factories == null) return; var factoriesCount = factories.Length; var propertySystem = DSPGame.propertySystem; List factoriesToRemove = null; foreach (var factoryIndex in SignalBeltFactoryIndices) { if (factoryIndex >= factoriesCount) { if (factoriesToRemove == null) factoriesToRemove = [factoryIndex]; else factoriesToRemove.Add(factoryIndex); continue; } var signalBelts = GetSignalBelts(factoryIndex); if (signalBelts == null) continue; var factory = factories[factoryIndex]; if (factory == null) continue; var cargoTraffic = factory.cargoTraffic; var beltCount = cargoTraffic.beltCursor; List beltsToRemove = null; foreach (var kvp in signalBelts) { if (kvp.Key >= beltCount) { if (beltsToRemove == null) beltsToRemove = [kvp.Key]; else beltsToRemove.Add(kvp.Key); continue; } ref var belt = ref cargoTraffic.beltPool[kvp.Key]; var cargoPath = cargoTraffic.GetCargoPath(belt.segPathId); var itemIdx = kvp.Value; if (cargoPath == null) continue; var itemId = DarkFogItemIds[itemIdx]; var consume = (byte)Math.Min(DarkFogItemsInVoid[itemIdx], 4); if (consume < 4) { var metaverse = propertySystem.GetItemAvaliableProperty(_clusterSeedKey, 6006); if (metaverse > 0) { if (metaverse > 10) metaverse = 10; propertySystem.AddItemConsumption(_clusterSeedKey, 6006, metaverse); var mainPlayer = GameMain.mainPlayer; GameMain.history.AddPropertyItemConsumption(6006, metaverse, true); var count = DarkFogItemExchangeRate[itemIdx] * metaverse; DarkFogItemsInVoid[itemIdx] += count; consume = (byte)Math.Min(DarkFogItemsInVoid[itemIdx], 4); mainPlayer.mecha.AddProductionStat(itemId, count, mainPlayer.nearestFactory); } } if (consume > 0 && cargoPath.TryInsertItem(belt.segIndex + belt.segPivotOffset, itemId, consume, 0)) DarkFogItemsInVoid[itemIdx] -= consume; } if (beltsToRemove == null) continue; foreach (var beltId in beltsToRemove) signalBelts.Remove(beltId); if (signalBelts.Count > 0) continue; if (factoriesToRemove == null) factoriesToRemove = [factoryIndex]; else factoriesToRemove.Add(factoryIndex); } if (factoriesToRemove == null) return; foreach (var factoryIndex in factoriesToRemove) { RemovePlanetSignalBelts(factoryIndex); } } } private class TankFastFillInAndTakeOut : PatchImpl { private static readonly CodeInstruction[] MultiplierWithCountCheck = [ new(OpCodes.Ldsfld, AccessTools.Field(typeof(FactoryPatch), nameof(_tankFastFillInAndTakeOutMultiplierRealValue))), new(OpCodes.Call, AccessTools.Method(typeof(Math), nameof(Math.Min), [typeof(int), typeof(int)])) ]; private static readonly CodeInstruction GetRealCount = new(OpCodes.Ldsfld, AccessTools.Field(typeof(FactoryPatch), nameof(_tankFastFillInAndTakeOutMultiplierRealValue))); [HarmonyTranspiler] [HarmonyPatch(typeof(PlanetFactory), nameof(PlanetFactory.EntityFastFillIn))] private static IEnumerable PlanetFactory_EntityFastFillIn_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(ci => ci.IsStloc()), new CodeMatch(OpCodes.Ldc_I4_2), new CodeMatch(ci => ci.IsStloc()) ).Advance(1).RemoveInstruction().InsertAndAdvance(GetRealCount).MatchForward(false, new CodeMatch(OpCodes.Ldc_I4_1), new CodeMatch(ci => ci.Branches(out _)), new CodeMatch(OpCodes.Ldc_I4_1), new CodeMatch(ci => ci.Branches(out _)), new CodeMatch(OpCodes.Ldc_I4_2), new CodeMatch(ci => ci.IsStloc()) ).RemoveInstructions(5).Insert(MultiplierWithCountCheck); return matcher.InstructionEnumeration(); } [HarmonyTranspiler] [HarmonyPatch(typeof(PlanetFactory), nameof(PlanetFactory.EntityFastTakeOut))] private static IEnumerable PlanetFactory_EntityFastTakeOut_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(OpCodes.Ldc_I4_2), new CodeMatch(OpCodes.Ldc_I4_0), new CodeMatch(ci => ci.opcode == OpCodes.Ldloca || ci.opcode == OpCodes.Ldloca_S) ).Advance(1).RemoveInstruction().InsertAndAdvance(GetRealCount).MatchForward(false, new CodeMatch(OpCodes.Ldc_I4_1), new CodeMatch(ci => ci.opcode == OpCodes.Bgt || ci.opcode == OpCodes.Bgt_S), new CodeMatch(OpCodes.Ldc_I4_1), new CodeMatch(ci => ci.opcode == OpCodes.Br || ci.opcode == OpCodes.Br_S), new CodeMatch(OpCodes.Ldc_I4_2), new CodeMatch(ci => ci.IsLdloc()) ).RemoveInstructions(5).Insert(MultiplierWithCountCheck); return matcher.InstructionEnumeration(); } [HarmonyTranspiler] [HarmonyPatch(typeof(UITankWindow), nameof(UITankWindow._OnUpdate))] private static IEnumerable UITankWindow__OnUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldc_I4_1), new CodeMatch(ci => ci.opcode == OpCodes.Bgt || ci.opcode == OpCodes.Bgt_S), new CodeMatch(OpCodes.Ldc_I4_1), new CodeMatch(ci => ci.opcode == OpCodes.Br || ci.opcode == OpCodes.Br_S), new CodeMatch(OpCodes.Ldc_I4_2), new CodeMatch(ci => ci.IsStloc()) ); matcher.Repeat(m => m.RemoveInstructions(5).InsertAndAdvance(MultiplierWithCountCheck)); return matcher.InstructionEnumeration(); } [HarmonyPrefix] [HarmonyPatch(typeof(TankComponent), nameof(TankComponent.TickOutput))] private static bool TankComponent_TickOutput_Prefix(ref TankComponent __instance, PlanetFactory factory) { if (!__instance.outputSwitch || __instance.fluidCount <= 0) return false; var lastTankId = __instance.lastTankId; if (lastTankId <= 0) return false; var factoryStorage = factory.factoryStorage; ref var tankComponent = ref factoryStorage.tankPool[lastTankId]; if (!tankComponent.inputSwitch || (tankComponent.fluidId > 0 && tankComponent.fluidId != __instance.fluidId)) return false; var left = tankComponent.fluidCapacity - tankComponent.fluidCount; if (left <= 0) return false; if (tankComponent.fluidId == 0) tankComponent.fluidId = __instance.fluidId; var takeOut = Math.Min(left, _tankFastFillInAndTakeOutMultiplierRealValue); if (takeOut >= __instance.fluidCount) { tankComponent.fluidCount += __instance.fluidCount; tankComponent.fluidInc += __instance.fluidInc; __instance.fluidId = 0; __instance.fluidCount = 0; __instance.fluidInc = 0; } else { var takeInc = __instance.split_inc(ref __instance.fluidCount, ref __instance.fluidInc, takeOut); tankComponent.fluidCount += takeOut; tankComponent.fluidInc += takeInc; } return false; } } private class TweakBuildingBuffer : PatchImpl { public static void RefreshAssemblerBufferMultipliers() { if (!TweakBuildingBufferEnabled.Value) return; /* re-patch to use new value */ var patch = Instance._patch; patch.Unpatch(AccessTools.Method(typeof(AssemblerComponent), nameof(AssemblerComponent.UpdateNeeds)), AccessTools.Method(typeof(TweakBuildingBuffer), nameof(AssemblerComponent_UpdateNeeds_Transpiler))); patch.Patch(AccessTools.Method(typeof(AssemblerComponent), nameof(AssemblerComponent.UpdateNeeds)), null, null, new HarmonyMethod(typeof(TweakBuildingBuffer), nameof(AssemblerComponent_UpdateNeeds_Transpiler))); } public static void RefreshLabBufferMaxCountForAssemble() { if (!TweakBuildingBufferEnabled.Value) return; /* re-patch to use new value */ var patch = Instance._patch; patch.Unpatch(AccessTools.Method(typeof(LabComponent), nameof(LabComponent.UpdateNeedsAssemble)), AccessTools.Method(typeof(TweakBuildingBuffer), nameof(LabComponent_UpdateNeedsAssemble_Transpiler))); patch.Patch(AccessTools.Method(typeof(LabComponent), nameof(LabComponent.UpdateNeedsAssemble)), null, null, new HarmonyMethod(typeof(TweakBuildingBuffer), nameof(LabComponent_UpdateNeedsAssemble_Transpiler))); } public static void RefreshLabBufferMaxCountForResearch() { if (!TweakBuildingBufferEnabled.Value) return; /* re-patch to use new value */ var patch = Instance._patch; patch.Unpatch(AccessTools.Method(typeof(LabComponent), nameof(LabComponent.UpdateNeedsResearch)), AccessTools.Method(typeof(TweakBuildingBuffer), nameof(LabComponent_UpdateNeedsResearch_Transpiler))); patch.Patch(AccessTools.Method(typeof(LabComponent), nameof(LabComponent.UpdateNeedsResearch)), null, null, new HarmonyMethod(typeof(TweakBuildingBuffer), nameof(LabComponent_UpdateNeedsResearch_Transpiler))); } public static void RefreshReceiverBufferCount() { if (!TweakBuildingBufferEnabled.Value) return; /* re-patch to use new value */ var patch = Instance._patch; patch.Unpatch(AccessTools.Method(typeof(PowerGeneratorComponent), nameof(PowerGeneratorComponent.GameTick_Gamma)), AccessTools.Method(typeof(TweakBuildingBuffer), nameof(PowerGeneratorComponent_GameTick_Gamma_Transpiler))); patch.Patch(AccessTools.Method(typeof(PowerGeneratorComponent), nameof(PowerGeneratorComponent.GameTick_Gamma)), null, null, new HarmonyMethod(typeof(TweakBuildingBuffer), nameof(PowerGeneratorComponent_GameTick_Gamma_Transpiler))); } public static void RefreshEjectorBufferCount() { if (!TweakBuildingBufferEnabled.Value) return; /* re-patch to use new value */ var patch = Instance._patch; patch.Unpatch(AccessTools.Method(typeof(EjectorComponent), nameof(EjectorComponent.InternalUpdate)), AccessTools.Method(typeof(TweakBuildingBuffer), nameof(EjectorComponent_InternalUpdate_Transpiler))); patch.Patch(AccessTools.Method(typeof(EjectorComponent), nameof(EjectorComponent.InternalUpdate)), null, null, new HarmonyMethod(typeof(TweakBuildingBuffer), nameof(EjectorComponent_InternalUpdate_Transpiler))); } public static void RefreshSiloBufferCount() { if (!TweakBuildingBufferEnabled.Value) return; /* re-patch to use new value */ var patch = Instance._patch; patch.Unpatch(AccessTools.Method(typeof(SiloComponent), nameof(SiloComponent.InternalUpdate)), AccessTools.Method(typeof(TweakBuildingBuffer), nameof(SiloComponent_InternalUpdate_Transpiler))); patch.Patch(AccessTools.Method(typeof(SiloComponent), nameof(SiloComponent.InternalUpdate)), null, null, new HarmonyMethod(typeof(TweakBuildingBuffer), nameof(SiloComponent_InternalUpdate_Transpiler))); } [HarmonyTranspiler] [HarmonyPatch(typeof(PowerGeneratorComponent), nameof(PowerGeneratorComponent.GameTick_Gamma))] private static IEnumerable PowerGeneratorComponent_GameTick_Gamma_Transpiler(IEnumerable instructions, ILGenerator generator) { /* * Patch: * bool flag3 = keyFrame && useIon && (float)this.catalystPoint < 72000f; * To: * bool flag3 = keyFrame && useIon && this.catalystPoint < 3600 * ReceiverBufferCount.Value; */ var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(PowerGeneratorComponent), nameof(PowerGeneratorComponent.catalystPoint))), new CodeMatch(OpCodes.Conv_R4), new CodeMatch(OpCodes.Ldc_R4, 72000f), new CodeMatch(OpCodes.Clt) ); matcher.Advance(2).RemoveInstructions(2).Insert(new CodeInstruction(OpCodes.Ldc_I4, ReceiverBufferCount.Value * 3600)); return matcher.InstructionEnumeration(); } [HarmonyTranspiler] [HarmonyPatch(typeof(AssemblerComponent), nameof(AssemblerComponent.UpdateNeeds))] private static IEnumerable AssemblerComponent_UpdateNeeds_Transpiler(IEnumerable instructions, ILGenerator generator) { /* * Patch: * int num2 = this.speedOverride * 180 / this.timeSpend + 1; * if (num2 < 2) * { * num2 = 2; * } * To: * int num2 = this.speedOverride * 60 * (AssemblerBufferTimeMultiplier.Value - 1) * 60 / this.timeSpend + 1; * if (num2 < AssemblerBufferMininumMultiplier.Value) * { * num2 = AssemblerBufferMininumMultiplier.Value; * } */ var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(AssemblerComponent), nameof(AssemblerComponent.speedOverride))), new CodeMatch(OpCodes.Ldc_I4, 180), new CodeMatch(OpCodes.Mul) ); matcher.Advance(2).Operand = (AssemblerBufferTimeMultiplier.Value - 1) * 60; matcher.Advance(2).MatchForward(false, new CodeMatch(OpCodes.Ldc_I4_2), new CodeMatch(ci => ci.opcode == OpCodes.Bge_S || ci.opcode == OpCodes.Bge), new CodeMatch(OpCodes.Ldc_I4_2) ); matcher.Operand = AssemblerBufferMininumMultiplier.Value; matcher.Advance(2).Operand = AssemblerBufferMininumMultiplier.Value; return matcher.InstructionEnumeration(); } [HarmonyTranspiler] [HarmonyPatch(typeof(LabComponent), nameof(LabComponent.UpdateNeedsAssemble))] private static IEnumerable LabComponent_UpdateNeedsAssemble_Transpiler(IEnumerable instructions, ILGenerator generator) { /* * Patch: * int num2 = ((this.timeSpend > 5400000) ? 6 : * (3 * ((this.speedOverride + 5001) / 10000) + 3)); * To: * int num2 = ((this.timeSpend > 5400000) ? LabBufferMaxCountForAssemble.Value : * (LabBufferExtraCountForAdvancedAssemble.Value * ((this.speedOverride + 5001) / 10000) + (LabBufferMaxCountForAssemble.Value - LabBufferExtraCountForAdvancedAssemble.Value))); */ var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(LabComponent), nameof(LabComponent.timeSpend))), new CodeMatch(OpCodes.Ldc_I4, 5400000), new CodeMatch(ci => ci.opcode == OpCodes.Bgt_S || ci.opcode == OpCodes.Bgt), new CodeMatch(OpCodes.Ldc_I4_3) ); var extraCount = LabBufferExtraCountForAdvancedAssemble.Value; matcher.Advance(4).SetAndAdvance(OpCodes.Ldc_I4, extraCount); matcher.MatchForward(false, new CodeMatch(OpCodes.Div), new CodeMatch(OpCodes.Mul), new CodeMatch(OpCodes.Ldc_I4_3), new CodeMatch(OpCodes.Add), new CodeMatch(ci => ci.opcode == OpCodes.Br || ci.opcode == OpCodes.Br_S), new CodeMatch(OpCodes.Ldc_I4_6), new CodeMatch(ci => ci.IsStloc()) ); var maxCount = LabBufferMaxCountForAssemble.Value; matcher.Advance(2).SetAndAdvance(OpCodes.Ldc_I4, maxCount > extraCount ? maxCount - extraCount : 2); matcher.Advance(2).SetAndAdvance(OpCodes.Ldc_I4, maxCount); return matcher.InstructionEnumeration(); } [HarmonyTranspiler] [HarmonyPatch(typeof(LabComponent), nameof(LabComponent.UpdateNeedsResearch))] private static IEnumerable LabComponent_UpdateNeedsResearch_Transpiler(IEnumerable instructions, ILGenerator generator) { /* * Patch: * this.needs[0] = ((this.matrixServed[0] < 36000) ? 6001 : 0); * this.needs[1] = ((this.matrixServed[1] < 36000) ? 6002 : 0); * this.needs[2] = ((this.matrixServed[2] < 36000) ? 6003 : 0); * this.needs[3] = ((this.matrixServed[3] < 36000) ? 6004 : 0); * this.needs[4] = ((this.matrixServed[4] < 36000) ? 6005 : 0); * this.needs[5] = ((this.matrixServed[5] < 36000) ? 6006 : 0); * To: * this.needs[0] = ((this.matrixServed[0] < LabBufferMaxCountForResearch.Value * 3600) ? 6001 : 0); * this.needs[1] = ((this.matrixServed[1] < LabBufferMaxCountForResearch.Value * 3600) ? 6002 : 0); * this.needs[2] = ((this.matrixServed[2] < LabBufferMaxCountForResearch.Value * 3600) ? 6003 : 0); * this.needs[3] = ((this.matrixServed[3] < LabBufferMaxCountForResearch.Value * 3600) ? 6004 : 0); * this.needs[4] = ((this.matrixServed[4] < LabBufferMaxCountForResearch.Value * 3600) ? 6005 : 0); * this.needs[5] = ((this.matrixServed[5] < LabBufferMaxCountForResearch.Value * 3600) ? 6006 : 0); */ var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldc_I4, 36000) ); matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4, LabBufferMaxCountForResearch.Value * 3600)); return matcher.InstructionEnumeration(); } [HarmonyTranspiler] [HarmonyPatch(typeof(EjectorComponent), nameof(EjectorComponent.InternalUpdate))] private static IEnumerable EjectorComponent_InternalUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(EjectorComponent), nameof(EjectorComponent.bulletCount))), new CodeMatch(ci => (ci.opcode == OpCodes.Ldc_I4 || ci.opcode == OpCodes.Ldc_I4_S) && ci.OperandIs(20)) ); matcher.Advance(2).Set(OpCodes.Ldc_I4, EjectorBufferCount.Value); return matcher.InstructionEnumeration(); } [HarmonyTranspiler] [HarmonyPatch(typeof(SiloComponent), nameof(SiloComponent.InternalUpdate))] private static IEnumerable SiloComponent_InternalUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(SiloComponent), nameof(SiloComponent.bulletCount))), new CodeMatch(ci => (ci.opcode == OpCodes.Ldc_I4 || ci.opcode == OpCodes.Ldc_I4_S) && ci.OperandIs(20)) ); matcher.Advance(2).Operand = SiloBufferCount.Value; return matcher.InstructionEnumeration(); } } }