using System; using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; using BepInEx.Configuration; using CommonAPI.Systems; using HarmonyLib; using UnityEngine; using UnityEngine.EventSystems; using UXAssist.Common; namespace UXAssist; public static class FactoryPatch { public static ConfigEntry UnlimitInteractiveEnabled; public static ConfigEntry RemoveSomeConditionEnabled; public static ConfigEntry NightLightEnabled; public static ConfigEntry RemoveBuildRangeLimitEnabled; public static ConfigEntry LargerAreaForUpgradeAndDismantleEnabled; public static ConfigEntry LargerAreaForTerraformEnabled; public static ConfigEntry OffGridBuildingEnabled; public static ConfigEntry LogisticsCapacityTweaksEnabled; public static ConfigEntry TreatStackingAsSingleEnabled; public static ConfigEntry QuickBuildAndDismantleLabsEnabled; public static ConfigEntry ProtectVeinsFromExhaustionEnabled; public static ConfigEntry DoNotRenderEntitiesEnabled; public static ConfigEntry DragBuildPowerPolesEnabled; public static ConfigEntry AllowOverflowInLogisticsEnabled; private static PressKeyBind _doNotRenderEntitiesKey; private static Harmony _factoryPatch; public static void Init() { _doNotRenderEntitiesKey = KeyBindings.RegisterKeyBinding(new BuiltinKey { key = new CombineKey(0, 0, ECombineKeyAction.OnceClick, true), conflictGroup = KeyBindConflict.MOVEMENT | KeyBindConflict.FLYING | KeyBindConflict.SAILING | KeyBindConflict.BUILD_MODE_1 | KeyBindConflict.KEYBOARD_KEYBIND, name = "ToggleDoNotRenderEntities", canOverride = true } ); I18N.Add("KEYToggleDoNotRenderEntities", "Toggle Do Not Render Factory Entities", "切换不渲染工厂建筑实体"); UnlimitInteractiveEnabled.SettingChanged += (_, _) => UnlimitInteractive.Enable(UnlimitInteractiveEnabled.Value); RemoveSomeConditionEnabled.SettingChanged += (_, _) => RemoveSomeConditionBuild.Enable(RemoveSomeConditionEnabled.Value); NightLightEnabled.SettingChanged += (_, _) => NightLight.Enable(NightLightEnabled.Value); RemoveBuildRangeLimitEnabled.SettingChanged += (_, _) => RemoveBuildRangeLimit.Enable(RemoveBuildRangeLimitEnabled.Value); LargerAreaForUpgradeAndDismantleEnabled.SettingChanged += (_, _) => LargerAreaForUpgradeAndDismantle.Enable(LargerAreaForUpgradeAndDismantleEnabled.Value); LargerAreaForTerraformEnabled.SettingChanged += (_, _) => LargerAreaForTerraform.Enable(LargerAreaForTerraformEnabled.Value); OffGridBuildingEnabled.SettingChanged += (_, _) => OffGridBuilding.Enable(OffGridBuildingEnabled.Value); LogisticsCapacityTweaksEnabled.SettingChanged += (_, _) => LogisticsCapacityTweaks.Enable(LogisticsCapacityTweaksEnabled.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); AllowOverflowInLogisticsEnabled.SettingChanged += (_, _) => AllowOverflowInLogistics.Enable(AllowOverflowInLogisticsEnabled.Value); LogisticsCapacityTweaksEnabled.SettingChanged += (_, _) => LogisticsCapacityTweaks.Enable(LogisticsCapacityTweaksEnabled.Value); 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); LogisticsCapacityTweaks.Enable(LogisticsCapacityTweaksEnabled.Value); TreatStackingAsSingle.Enable(TreatStackingAsSingleEnabled.Value); QuickBuildAndDismantleLab.Enable(QuickBuildAndDismantleLabsEnabled.Value); ProtectVeinsFromExhaustion.Enable(ProtectVeinsFromExhaustionEnabled.Value); DoNotRenderEntities.Enable(DoNotRenderEntitiesEnabled.Value); DragBuildPowerPoles.Enable(DragBuildPowerPolesEnabled.Value); AllowOverflowInLogistics.Enable(AllowOverflowInLogisticsEnabled.Value); LogisticsCapacityTweaks.Enable(LogisticsCapacityTweaksEnabled.Value); _factoryPatch ??= Harmony.CreateAndPatchAll(typeof(FactoryPatch)); } public static void Uninit() { RemoveSomeConditionBuild.Enable(false); UnlimitInteractive.Enable(false); NightLight.Enable(false); RemoveBuildRangeLimit.Enable(false); LargerAreaForUpgradeAndDismantle.Enable(false); LargerAreaForTerraform.Enable(false); OffGridBuilding.Enable(false); LogisticsCapacityTweaks.Enable(false); TreatStackingAsSingle.Enable(false); QuickBuildAndDismantleLab.Enable(false); ProtectVeinsFromExhaustion.Enable(false); DoNotRenderEntities.Enable(false); DragBuildPowerPoles.Enable(false); AllowOverflowInLogistics.Enable(false); LogisticsCapacityTweaks.Enable(false); _factoryPatch?.UnpatchSelf(); _factoryPatch = null; } public static void OnUpdate() { if (_doNotRenderEntitiesKey.keyValue) DoNotRenderEntitiesEnabled.Value = !DoNotRenderEntitiesEnabled.Value; } [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 static class NightLight { private static Harmony _patch; private const float NightLightAngleX = -8; private const float NightLightAngleY = -2; public static bool Enabled; private static bool _nightlightInitialized; private static bool _mechaOnEarth; private static AnimationState _sail; private static Light _sunlight; public static void Enable(bool on) { if (on) { Enabled = _mechaOnEarth; _patch ??= Harmony.CreateAndPatchAll(typeof(NightLight)); return; } Enabled = false; _patch?.UnpatchSelf(); _patch = null; if (_sunlight == null) return; _sunlight.transform.localEulerAngles = new Vector3(0f, 180f); } public static void LateUpdate() { if (_patch == null) return; switch (_nightlightInitialized) { case false: Ready(); break; case true: Go(); break; } } private static void Ready() { if (!GameMain.isRunning || !GameMain.mainPlayer.controller.model.gameObject.activeInHierarchy) return; if (_sail == null) { _sail = GameMain.mainPlayer.animator.sails[GameMain.mainPlayer.animator.sailAnimIndex]; } _nightlightInitialized = true; } private static void Go() { if (!GameMain.isRunning) { End(); return; } if (_sail && _sail.enabled) { _mechaOnEarth = false; Enabled = false; if (!_sunlight || !_sunlight.transform) return; _sunlight.transform.localEulerAngles = new Vector3(0f, 180f); _sunlight = null; return; } if (!_mechaOnEarth) { if (!_sunlight) { var simu = GameMain.universeSimulator; if (simu) _sunlight = simu.LocalStarSimulator()?.sunLight; if (!_sunlight) return; } _mechaOnEarth = true; Enabled = NightLightEnabled.Value; } if (Enabled && _sunlight) { _sunlight.transform.rotation = Quaternion.LookRotation(-GameMain.mainPlayer.transform.up + GameMain.mainPlayer.transform.forward * NightLightAngleX / 10f + GameMain.mainPlayer.transform.right * NightLightAngleY / 10f); } } private static void End() { _mechaOnEarth = false; Enabled = false; if (_sunlight != null) { _sunlight.transform.localEulerAngles = new Vector3(0f, 180f); _sunlight = null; } _sail = null; _nightlightInitialized = false; } [HarmonyTranspiler] [HarmonyPatch(typeof(StarSimulator), "LateUpdate")] private static IEnumerable StarSimulator_LateUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) { // var vec = NightlightEnabled ? GameMain.mainPlayer.transform.up : __instance.transform.forward; 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(NightLight.Enabled))), 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), "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(NightLight.Enabled))), 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 static class UnlimitInteractive { private static Harmony _patch; public static void Enable(bool enable) { if (enable) { _patch ??= Harmony.CreateAndPatchAll(typeof(UnlimitInteractive)); return; } _patch?.UnpatchSelf(); _patch = null; } [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 static class RemoveSomeConditionBuild { private static Harmony _patch; public static void Enable(bool on) { if (on) { _patch ??= Harmony.CreateAndPatchAll(typeof(RemoveSomeConditionBuild)); return; } _patch?.UnpatchSelf(); _patch = null; } [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(instr => instr.opcode == OpCodes.Brtrue || instr.opcode == OpCodes.Brtrue_S), new CodeMatch(OpCodes.Ldloca_S), new CodeMatch(OpCodes.Call, AccessTools.PropertyGetter(typeof(Vector3), nameof(Vector3.magnitude))) ); var jumpPos = matcher.InstructionAt(3).operand; var labels = matcher.Labels; matcher.Labels = []; /* Insert: br 2358 (1C12) ldloc.s V_8 (8) */ matcher.Insert(new CodeInstruction(OpCodes.Br, jumpPos).WithLabels(labels)); 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(OpCodes.Ldloc_S), new CodeMatch(OpCodes.Ldloc_S), new CodeMatch(instr => instr.opcode == OpCodes.Brtrue_S || instr.opcode == OpCodes.Brtrue), new CodeMatch(instr => instr.opcode == OpCodes.Ldc_I4_S && instr.OperandIs(19)), new CodeMatch(instr => instr.opcode == OpCodes.Br_S || instr.opcode == OpCodes.Br), new CodeMatch(instr => instr.opcode == OpCodes.Ldc_I4_S && instr.OperandIs(18)), 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 >= 16 and <= 20), 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 static class RemoveBuildRangeLimit { private static Harmony _patch; public static void Enable(bool enable) { if (enable) { _patch ??= Harmony.CreateAndPatchAll(typeof(RemoveBuildRangeLimit)); } else { _patch?.UnpatchSelf(); _patch = null; } 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 static class LargerAreaForUpgradeAndDismantle { private static Harmony _patch; public static void Enable(bool enable) { if (enable) { _patch ??= Harmony.CreateAndPatchAll(typeof(LargerAreaForUpgradeAndDismantle)); return; } _patch?.UnpatchSelf(); _patch = null; } [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 static class LargerAreaForTerraform { private static Harmony _patch; public static void Enable(bool enable) { if (enable) { _patch ??= Harmony.CreateAndPatchAll(typeof(LargerAreaForTerraform)); return; } _patch?.UnpatchSelf(); _patch = null; } [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 static class OffGridBuilding { private static Harmony _patch; private const float SteppedRotationDegrees = 15f; public static void Enable(bool enable) { if (enable) { _patch ??= Harmony.CreateAndPatchAll(typeof(OffGridBuilding)); return; } _patch?.UnpatchSelf(); _patch = null; } private static bool _initialized; [HarmonyPostfix, HarmonyPatch(typeof(UIRoot), "_OnOpen")] public static void UIRoot__OnOpen_Postfix() { 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) { 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(UnityEngine.UI.Text), nameof(UnityEngine.UI.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) { 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._ignoreGrid)))), 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._ignoreGrid))), 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))), 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.Call, AccessTools.PropertyGetter(typeof(VFInput), nameof(VFInput._switchModelStyle))), new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(VFInput.InputValue), nameof(VFInput.InputValue.pressing))), 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(OffGridBuilding.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 static class LogisticsCapacityTweaks { private static Harmony _patch; public static void Enable(bool enable) { if (enable) { _patch ??= Harmony.CreateAndPatchAll(typeof(LogisticsCapacityTweaks)); return; } _patch?.UnpatchSelf(); _patch = null; } private static KeyCode _lastKey = KeyCode.None; private static long _nextKeyTick; private static bool _skipNextEvent; private static bool UpdateKeyPressed(KeyCode code) { if (!Input.GetKey(code)) return false; if (code != _lastKey) { _lastKey = code; _nextKeyTick = GameMain.instance.timei + 30; return true; } var currTick = GameMain.instance.timei; if (_nextKeyTick > currTick) return false; _nextKeyTick = currTick + 4; return true; } public static void UpdateInput() { if (_lastKey != KeyCode.None && Input.GetKeyUp(_lastKey)) { _lastKey = KeyCode.None; } if (VFInput.shift) return; var ctrl = VFInput.control; var alt = VFInput.alt; if (ctrl && alt) return; int delta; if (UpdateKeyPressed(KeyCode.LeftArrow)) { if (ctrl) delta = -100000; else if (alt) delta = -1000; else delta = -10; } else if (UpdateKeyPressed(KeyCode.RightArrow)) { if (ctrl) delta = 100000; else if (alt) delta = 1000; else delta = 10; } else if (UpdateKeyPressed(KeyCode.DownArrow)) { if (ctrl) delta = -1000000; else if (alt) delta = -10000; else delta = -100; } else if (UpdateKeyPressed(KeyCode.UpArrow)) { if (ctrl) delta = 1000000; else if (alt) delta = 10000; else delta = 100; } else { return; } var targets = new List(); EventSystem.current.RaycastAll(new PointerEventData(EventSystem.current) { position = Input.mousePosition }, targets); foreach (var target in targets) { var stationStorage = target.gameObject.GetComponentInParent(); if (stationStorage is null) continue; var station = stationStorage.station; ref var storage = ref station.storage[stationStorage.index]; var oldMax = storage.max; var newMax = oldMax + delta; if (newMax < 0) { newMax = 0; } else { int itemCountMax; if (AllowOverflowInLogisticsEnabled.Value) { itemCountMax = 90000000; } else { var modelProto = LDB.models.Select(stationStorage.stationWindow.factory.entityPool[station.entityId].modelIndex); itemCountMax = 0; if (modelProto != null) { itemCountMax = modelProto.prefabDesc.stationMaxItemCount; } itemCountMax += station.isStellar ? GameMain.history.remoteStationExtraStorage : GameMain.history.localStationExtraStorage; } if (newMax > itemCountMax) { newMax = itemCountMax; } } storage.max = newMax; _skipNextEvent = oldMax / 100 != newMax / 100; break; } } [HarmonyPrefix] [HarmonyPatch(typeof(UIStationStorage), nameof(UIStationStorage.OnMaxSliderValueChange))] private static bool UIStationStorage_OnMaxSliderValueChange_Prefix() { if (!_skipNextEvent) return true; _skipNextEvent = false; return false; } [HarmonyPrefix] [HarmonyPatch(typeof(PlanetTransport), nameof(PlanetTransport.OnTechFunctionUnlocked))] private static bool PlanetTransport_OnTechFunctionUnlocked_Prefix(PlanetTransport __instance, int _funcId, double _valuelf, int _level) { switch (_funcId) { case 30: { var stationPool = __instance.stationPool; var factory = __instance.factory; var history = GameMain.history; for (var i = __instance.stationCursor - 1; i > 0; i--) { if (stationPool[i] == null || stationPool[i].id != i || (stationPool[i].isStellar && !stationPool[i].isCollector && !stationPool[i].isVeinCollector)) continue; var modelIndex = factory.entityPool[stationPool[i].entityId].modelIndex; var maxCount = LDB.models.Select(modelIndex).prefabDesc.stationMaxItemCount; var oldMaxCount = maxCount + history.localStationExtraStorage - _valuelf; var intOldMaxCount = (int)Math.Round(oldMaxCount); var ratio = (maxCount + history.localStationExtraStorage) / oldMaxCount; var storage = stationPool[i].storage; for (var j = storage.Length - 1; j >= 0; j--) { if (storage[j].max + 10 < intOldMaxCount) continue; storage[j].max = Mathf.RoundToInt((float)(storage[j].max * ratio / 50.0)) * 50; } } break; } case 31: { var stationPool = __instance.stationPool; var factory = __instance.factory; var history = GameMain.history; for (var i = __instance.stationCursor - 1; i > 0; i--) { if (stationPool[i] == null || stationPool[i].id != i || !stationPool[i].isStellar || stationPool[i].isCollector || stationPool[i].isVeinCollector) continue; var modelIndex = factory.entityPool[stationPool[i].entityId].modelIndex; var maxCount = LDB.models.Select(modelIndex).prefabDesc.stationMaxItemCount; var oldMaxCount = maxCount + history.remoteStationExtraStorage - _valuelf; var intOldMaxCount = (int)Math.Round(oldMaxCount); var ratio = (maxCount + history.remoteStationExtraStorage) / oldMaxCount; var storage = stationPool[i].storage; for (var j = storage.Length - 1; j >= 0; j--) { if (storage[j].max + 10 < intOldMaxCount) continue; storage[j].max = Mathf.RoundToInt((float)(storage[j].max * ratio / 100.0)) * 100; } } break; } } return false; } } public static class TreatStackingAsSingle { private static Harmony _patch; public static void Enable(bool enable) { if (enable) { _patch ??= Harmony.CreateAndPatchAll(typeof(TreatStackingAsSingle)); return; } _patch?.UnpatchSelf(); _patch = null; } [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 static class QuickBuildAndDismantleLab { private static Harmony _patch; public static void Enable(bool enable) { if (enable) { _patch ??= Harmony.CreateAndPatchAll(typeof(QuickBuildAndDismantleLab)); return; } _patch?.UnpatchSelf(); _patch = null; } private static bool DetermineMoreLabsForDismantle(BuildTool dismantle, int id) { if (!VFInput._chainReaction) return true; var factory = dismantle.factory; var prefDesc = dismantle.GetPrefabDesc(id); if (!prefDesc.isLab) 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 pose = dismantle.GetObjectPose(nextId); var desc = dismantle.GetPrefabDesc(nextId); var preview = new BuildPreview { item = dismantle.GetItemProto(nextId), 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 nextId, out _); if (nextId <= 0) break; var pose = dismantle.GetObjectPose(nextId); var desc = dismantle.GetPrefabDesc(nextId); var preview = new BuildPreview { item = dismantle.GetItemProto(nextId), 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) return; var levelMax = GameMain.history.labLevel; 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 static class ProtectVeinsFromExhaustion { public static int KeepVeinAmount = 100; public static float KeepOilSpeed = 1f; private static int _keepOilAmount = Math.Max((int)(KeepOilSpeed / 0.00004f + 0.5f), 2500); private static Harmony _patch; public static void Enable(bool enable) { if (enable) { _patch ??= Harmony.CreateAndPatchAll(typeof(ProtectVeinsFromExhaustion)); return; } _patch?.UnpatchSelf(); _patch = null; } [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 consumeCount = 0U; int veinId; int times; switch (__instance.type) { case EMinerType.Vein: if (__instance.veinCount <= 0) break; if (__instance.time <= __instance.period) { __instance.time += (int)(power * __instance.speedDamper * __instance.speed * miningSpeed * __instance.veinCount); consumeCount = 1U; } if (__instance.time < __instance.period) { break; } veinId = __instance.veins[__instance.currentVeinIndex]; lock (veinPool) { if (veinPool[veinId].id == 0) { __instance.RemoveVeinFromArray(__instance.currentVeinIndex); __instance.GetMinimumVeinAmount(factory, veinPool); if (__instance.veinCount > 1) { __instance.currentVeinIndex %= __instance.veinCount; } else { __instance.currentVeinIndex = 0; } __instance.time += (int)(power * __instance.speedDamper * __instance.speed * miningSpeed * __instance.veinCount); __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 (amount > KeepVeinAmount) { if (miningRate > 0f) { var usedCount = 0; var maxAllowed = amount - KeepVeinAmount; if (miningRate < 0.99999f) { for (var i = 0; i < times; i++) { __instance.seed = (uint)((__instance.seed % 2147483646U + 1U) * 48271UL % 2147483647UL) - 1U; usedCount += __instance.seed / 2147483646.0 < miningRate ? 1 : 0; outputCount++; if (usedCount == maxAllowed) { break; } } } else { outputCount = times > maxAllowed ? maxAllowed : times; usedCount = outputCount; } if (usedCount > 0) { var groupIndex = (int)veinPool[veinId].groupIndex; amount = veinPool[veinId].amount -= usedCount; if (amount < __instance.minimumVeinAmount) { __instance.minimumVeinAmount = amount; } var veinGroups = factory.veinGroups; 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, pos); __instance.RemoveVeinFromArray(__instance.currentVeinIndex); __instance.GetMinimumVeinAmount(factory, veinPool); } else { __instance.currentVeinIndex++; } } } else { outputCount = times; } __instance.productCount += outputCount; lock (productRegister) { productRegister[__instance.productId] += outputCount; factory.AddMiningFlagUnsafe(veinPool[veinId].type); factory.AddVeinMiningFlagUnsafe(veinPool[veinId].type); } } else if (amount <= 0) { __instance.RemoveVeinFromArray(__instance.currentVeinIndex); __instance.GetMinimumVeinAmount(factory, veinPool); } else { __instance.currentVeinIndex++; } __instance.time -= __instance.period * outputCount; if (__instance.veinCount > 1) { __instance.currentVeinIndex %= __instance.veinCount; } else { __instance.currentVeinIndex = 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); consumeCount = 1U; } if (__instance.time >= __instance.period && __instance.productCount < 50) { __instance.productId = veinPool[veinId].productId; times = __instance.time / __instance.period; var outputCount = 0; if (miningRate > 0f && amount > _keepOilAmount) { var usedCount = 0; var maxAllowed = amount - _keepOilAmount; for (var j = 0; j < times; j++) { __instance.seed = (uint)((__instance.seed % 2147483646U + 1U) * 48271UL % 2147483647UL) - 1U; usedCount += __instance.seed / 2147483646.0 < miningRate ? 1 : 0; outputCount++; } if (usedCount > 0) { if (usedCount > maxAllowed) { usedCount = maxAllowed; } amount = veinPool[veinId].amount -= usedCount; var veinGroups = factory.veinGroups; var groupIndex = veinPool[veinId].groupIndex; veinGroups[groupIndex].amount -= usedCount; factory.veinAnimPool[veinId].time = amount >= 25000 ? 0f : 1f - amount * VeinData.oilSpeedMultiplier; if (amount <= 2500) { factory.NotifyVeinExhausted((int)veinPool[veinId].type, veinPool[veinId].pos); } } } else if (_keepOilAmount <= 2500) { outputCount = times; } if (outputCount > 0) { __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); consumeCount = 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.productCount > 0 && __instance.insertTarget > 0 && __instance.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.productCount == 0 && __instance.type == EMinerType.Vein) { __instance.productId = 0; } } __result = consumeCount; return false; } } private static class DoNotRenderEntities { private static Harmony _patch; public static void Enable(bool enable) { if (enable) { _patch ??= Harmony.CreateAndPatchAll(typeof(DoNotRenderEntities)); return; } _patch?.UnpatchSelf(); _patch = null; } [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 static class DragBuildPowerPoles { private static Harmony _patch; private static readonly List OldDragBuild = []; private static readonly List OldDragBuildDist = []; private static readonly int[] PowerPoleIds = [2201, 2202, 2212]; public static void Enable(bool enable) { if (enable) { _patch ??= Harmony.CreateAndPatchAll(typeof(DragBuildPowerPoles)); FixProto(); return; } UnfixProto(); _patch?.UnpatchSelf(); _patch = null; } 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 powerPole = LDB.items.Select(id); if (powerPole?.prefabDesc == null) return; OldDragBuild.Add(powerPole.prefabDesc.dragBuild); OldDragBuildDist.Add(powerPole.prefabDesc.dragBuildDist); powerPole.prefabDesc.dragBuild = true; var distance = (id == 2201 ? LDB.items.Select(2202) : powerPole).prefabDesc.powerConnectDistance - 0.72f; powerPole.prefabDesc.dragBuildDist = new Vector2(distance, distance); } } private static void UnfixProto() { if (_patch == 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(); } [HarmonyPostfix, HarmonyPriority(Priority.Last)] [HarmonyPatch(typeof(GameMain), nameof(GameMain.Begin))] private static void GameMain_Begin_Postfix() { FixProto(); } [HarmonyPostfix, HarmonyPriority(Priority.Last)] [HarmonyPatch(typeof(GameMain), nameof(GameMain.End))] 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 intervalAll = interval.x; var radTotal = Mathf.Acos(Vector3.Dot(begin, end)); var distTotal = radTotal * planetRadius; var ignoreGrid = VFInput._ignoreGrid; var maxCount = snaps.Length; var finalCount = 0; var maxT = 1f - intervalAll * 0.5f / distTotal; 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 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, 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))) ).Advance(2).InsertAndAdvance(new CodeInstruction(OpCodes.Ldloc_S, 6)).SetInstructionAndAdvance(Transpilers.EmitDelegate((BuildTool_Click click, int i) => { if ((i & 1) == 0) return click.handItem; var id = click.handItem.ID; if (id != 2201 && id != 2202) return click.handItem; return LDB.items.Select(id ^ 3); })).Advance(3).InsertAndAdvance(new CodeInstruction(OpCodes.Ldloc_S, 6)).SetInstructionAndAdvance(Transpilers.EmitDelegate((BuildTool_Click click, int i) => { if ((i & 1) == 0) return click.handPrefabDesc; var id = click.handItem.ID; if (id != 2201 && id != 2202) return click.handPrefabDesc; return LDB.items.Select(id ^ 3).prefabDesc; })); return matcher.InstructionEnumeration(); } } private static class AllowOverflowInLogistics { private static Harmony _patch; public static void Enable(bool enable) { if (enable) { _patch ??= Harmony.CreateAndPatchAll(typeof(AllowOverflowInLogistics)); return; } _patch?.UnpatchSelf(); _patch = null; } // Do not check for overflow when try to send hand items into storages [HarmonyTranspiler] [HarmonyPatch(typeof(UIStationStorage), nameof(UIStationStorage.OnItemIconMouseDown))] private static IEnumerable UIStationStorage_OnItemIconMouseDown_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Call, AccessTools.PropertyGetter(typeof(LDB), nameof(LDB.items))), new CodeMatch(OpCodes.Ldarg_0) ); var pos = matcher.Pos; matcher.MatchForward(false, new CodeMatch(OpCodes.Ldloc_S), new CodeMatch(OpCodes.Stloc_S) ); var inst = matcher.InstructionAt(1).Clone(); var pos2 = matcher.Pos + 2; matcher.Start().Advance(pos); var labels = matcher.Labels; matcher.RemoveInstructions(pos2 - pos).Insert( new CodeInstruction(OpCodes.Ldloc_1).WithLabels(labels), new CodeInstruction(OpCodes.Callvirt, AccessTools.PropertyGetter(typeof(Player), nameof(Player.inhandItemCount))), inst ); return matcher.InstructionEnumeration(); } // Remove storage limit check [HarmonyTranspiler] [HarmonyPatch(typeof(PlanetTransport), nameof(PlanetTransport.SetStationStorage))] private static IEnumerable PlanetTransport_SetStationStorage_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(ci => ci.IsLdarg()), new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(OpCodes.Add), new CodeMatch(ci => ci.Branches(out _)), new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(OpCodes.Add), new CodeMatch(ci => ci.IsStarg()) ); var labels = matcher.Labels; matcher.RemoveInstructions(9).Labels.AddRange(labels); return matcher.InstructionEnumeration(); } } }