using System; using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; using HarmonyLib; using UnityEngine; using UnityEngine.UI; using UXAssist.Common; using GameLogicProc = UXAssist.Common.GameLogic; namespace UXAssist.Patches.Factory; internal static class BuildToolPatch { public static void Enable(bool enable) { BuildGizmoPatch.Enable(enable); OffGridBuilding.Enable(enable); TreatStackingAsSingle.Enable(enable); DragBuildPowerPoles.Enable(enable); TankFastFillInAndTakeOut.Enable(enable); PressShiftToTakeWholeBeltItems.Enable(enable); } private class BuildGizmoPatch : PatchImpl { // Harmony transpiler: ConnGizmoGraph_Constructor_Transpiler // Target: ConnGizmoGraph..ctor // Fallback: None — patch will fail loudly if the target method body changes. [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(); } // Harmony transpiler: ConnGizmoGraph_SetPointCount_Transpiler // Target: ConnGizmoGraph.SetPointCount // Fallback: None — patch will fail loudly if the target method body changes. [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(); } // Harmony transpiler: BuildTool_Path__OnInit_Transpiler // Target: BuildTool_Path._OnInit // Fallback: None — patch will fail loudly if the target method body changes. [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(); } // Harmony transpiler: BuildTool_Reform_Constructor_Transpiler // Target: BuildTool_Reform..ctor // Fallback: Checks CodeMatcher.IsInvalid/IsValid and returns original instructions on mismatch. [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(); } } internal 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; } // Harmony transpiler: UIEntityBriefInfo__OnUpdate_Transpiler // Target: UIEntityBriefInfo._OnUpdate // Fallback: None — patch will fail loudly if the target method body changes. [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; } // Harmony transpiler: AllowOffGridConstruction // Target: BuildTool_Click.UpdateRaycast, BuildTool_Click.DeterminePreviews // Fallback: Checks CodeMatcher.IsInvalid/IsValid and returns original instructions on mismatch. [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(); } // Harmony transpiler: PreventDraggingWhenOffGrid // Target: BuildTool_Click.DeterminePreviews // Fallback: Checks CodeMatcher.IsInvalid/IsValid and returns original instructions on mismatch. [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(); } // Harmony transpiler: AllowOffGridConstructionForPath // Target: BuildTool_Path.UpdateRaycast // Fallback: Checks CodeMatcher.IsInvalid/IsValid and returns original instructions on mismatch. [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(FactoryPatch._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; } } */ } internal class TreatStackingAsSingle : PatchImpl { // Harmony transpiler: MonitorComponent_InternalUpdate_Transpiler // Target: MonitorComponent.InternalUpdate // Fallback: None — patch will fail loudly if the target method body changes. [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(); } } internal 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 += OnGameBegin; GameLogicProc.OnGameEnd += OnGameEnd; FixProto(); } protected override void OnDisable() { UnfixProto(); GameLogicProc.OnGameEnd -= OnGameEnd; GameLogicProc.OnGameBegin -= OnGameBegin; } 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.8f; prefabDesc.dragBuildDist = new Vector2(distance, distance); } } private static void UnfixProto() { if (GetHarmony() == null || OldDragBuild.Count < PowerPoleIds.Length || 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 OnGameBegin() { _powerPoleProto ??= LDB.items.Select(2201); FixProto(); } private static void OnGameEnd() { UnfixProto(); _powerPoleProto = null; } 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; } // Harmony transpiler: BuildTool_Click_DeterminePreviews_Transpiler // Target: BuildTool_Click.DeterminePreviews // Fallback: None — patch will fail loudly if the target method body changes. [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 (click.handItem.ID != 2202 || (i & 1) == 0 || !FactoryPatch.DragBuildPowerPolesAlternatelyEnabled.Value) return click.handItem; return _powerPoleProto; })).Advance(3).InsertAndAdvance( new CodeInstruction(OpCodes.Ldloc_S, operand) ).SetInstructionAndAdvance(Transpilers.EmitDelegate((BuildTool_Click click, int i) => { if (click.handItem.ID != 2202 || (i & 1) == 0 || !FactoryPatch.DragBuildPowerPolesAlternatelyEnabled.Value) return click.handPrefabDesc; return _powerPoleProto.prefabDesc; })); matcher.MatchForward(false, new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(BuildPreview), nameof(BuildPreview.Clone))) ).Advance(2).InsertAndAdvance( new CodeInstruction(OpCodes.Ldloc_S, operand) ).SetInstructionAndAdvance(Transpilers.EmitDelegate((BuildPreview to, BuildPreview from, int i) => { if (from.item.ID != 2202 || (i & 1) == 0 || !FactoryPatch.DragBuildPowerPolesAlternatelyEnabled.Value) { to.Clone(from); return; } to.ResetAll(); to.item = _powerPoleProto; to.desc = _powerPoleProto.prefabDesc; to.needModel = _powerPoleProto.prefabDesc.lodCount > 0 && _powerPoleProto.prefabDesc.lodMeshes[0] != null; })); return matcher.InstructionEnumeration(); } private static ItemProto _powerPoleProto; } internal class TankFastFillInAndTakeOut : PatchImpl { private static readonly CodeInstruction[] MultiplierWithCountCheck = [ new(OpCodes.Ldsfld, AccessTools.Field(typeof(FactoryPatch), nameof(FactoryPatch._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(FactoryPatch._tankFastFillInAndTakeOutMultiplierRealValue))); // Harmony transpiler: PlanetFactory_EntityFastFillIn_Transpiler // Target: PlanetFactory.EntityFastFillIn // Fallback: None — patch will fail loudly if the target method body changes. [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(); } // Harmony transpiler: PlanetFactory_EntityFastTakeOut_Transpiler // Target: PlanetFactory.EntityFastTakeOut // Fallback: None — patch will fail loudly if the target method body changes. [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(); } // Harmony transpiler: UITankWindow__OnUpdate_Transpiler // Target: UITankWindow._OnUpdate // Fallback: None — patch will fail loudly if the target method body changes. [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, FactoryPatch._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; } } internal class PressShiftToTakeWholeBeltItems : PatchImpl { private static long nextTimei = 0; protected override void OnEnable() { GameLogicProc.OnGameBegin += OnGameBegin; } protected override void OnDisable() { GameLogicProc.OnGameBegin -= OnGameBegin; } private static void OnGameBegin() { nextTimei = 0; } // Harmony transpiler: VFInput_fastTransferWithEntityDown_Transpiler // Target: VFInput._fastTransferWithEntityDown (getter), VFInput._fastTransferWithEntityPress (getter) // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(VFInput), nameof(VFInput._fastTransferWithEntityDown), MethodType.Getter)] [HarmonyPatch(typeof(VFInput), nameof(VFInput._fastTransferWithEntityPress), MethodType.Getter)] private static IEnumerable VFInput_fastTransferWithEntityDown_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldsfld, AccessTools.Field(typeof(VFInput), nameof(VFInput.shift))), new CodeMatch(ci => ci.opcode == OpCodes.Brtrue || ci.opcode == OpCodes.Brtrue_S) ); var lables = matcher.Labels; matcher.RemoveInstructions(2); matcher.Labels.AddRange(lables); return matcher.InstructionEnumeration(); } // Harmony transpiler: PlayerAction_Inspect_GameTick_Transpiler // Target: PlayerAction_Inspect.GameTick // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PlayerAction_Inspect), nameof(PlayerAction_Inspect.GameTick))] private static IEnumerable PlayerAction_Inspect_GameTick_Transpiler(IEnumerable instructions, ILGenerator generator) { var matcher = new CodeMatcher(instructions, generator); matcher.MatchForward(false, new CodeMatch(OpCodes.Ldc_I4_1), new CodeMatch(OpCodes.Stfld, AccessTools.Field(typeof(PlayerAction_Inspect), nameof(PlayerAction_Inspect.fastFillIn))) ); matcher.SetAndAdvance(OpCodes.Ldsfld, AccessTools.Field(typeof(VFInput), nameof(VFInput.shift))).Insert( new CodeInstruction(OpCodes.Ldc_I4_0), new CodeInstruction(OpCodes.Ceq) ); var label0 = generator.DefineLabel(); var label1 = generator.DefineLabel(); matcher.Start().MatchForward(false, new CodeMatch(ci => ci.IsStloc()), new CodeMatch(OpCodes.Ldc_I4_0), new CodeMatch(ci => ci.IsStloc()), new CodeMatch(OpCodes.Ldloc_0), new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(ci => ci.IsLdloc()), new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(PlanetFactory), nameof(PlanetFactory.EntityFastTakeOut))) ).Advance(8).InsertAndAdvance( new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(VFInput), nameof(VFInput.shift))), new CodeInstruction(OpCodes.Brfalse_S, label0), new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(PressShiftToTakeWholeBeltItems), nameof(EntityFastTakeOutAlt))), new CodeInstruction(OpCodes.Br, label1) ).Labels.Add(label0); matcher.Advance(1).Labels.Add(label1); return matcher.InstructionEnumeration(); } private static void EntityFastTakeOutAlt(PlanetFactory factory, int entityId, bool toPackage, out ItemBundle itemBundle, out bool full) { if (factory._tmp_items == null) { factory._tmp_items = new ItemBundle(); } else { factory._tmp_items.Clear(); } itemBundle = factory._tmp_items; full = false; if (entityId == 0 || factory.entityPool[entityId].id != entityId) { return; } var main = GameMain.instance; if (main.timei < nextTimei) return; nextTimei = main.timei + 12; ref var entityData = ref factory.entityPool[entityId]; if (entityData.beltId <= 0) return; var cargoTraffic = factory.cargoTraffic; ref var belt = ref cargoTraffic.beltPool[entityData.beltId]; if (belt.id != entityData.beltId) return; HashSet pathIds = [belt.segPathId]; HashSet inserterIds = []; var includeBranches = FactoryPatch.PressShiftToTakeWholeBeltItemsIncludeBranches.Value; var includeInserters = FactoryPatch.PressShiftToTakeWholeBeltItemsIncludeInserters.Value; List pendingPathIds = [belt.segPathId]; Dictionary takeOutItems = []; var factorySystem = factory.factorySystem; while (pendingPathIds.Count > 0) { var lastIndex = pendingPathIds.Count - 1; var thisPathId = pendingPathIds[lastIndex]; pendingPathIds.RemoveAt(lastIndex); var path = cargoTraffic.GetCargoPath(thisPathId); if (path == null) continue; if (includeInserters) { foreach (var beltId in path.belts) { ref var b = ref cargoTraffic.beltPool[beltId]; if (b.id != beltId) return; // From WriteObjectConn: Only slot 4 to 11 is used for belt <-> inserter connections (method argument slot/otherSlot is -1 there) for (int cidx = 4; cidx < 12; cidx++) { factory.ReadObjectConn(b.entityId, cidx, out var isOutput, out var otherObjId, out var otherSlot); if (otherObjId <= 0) continue; var inserterId = factory.entityPool[otherObjId].inserterId; if (inserterId <= 0) continue; ref var inserter = ref factorySystem.inserterPool[inserterId]; if (inserter.id != inserterId) continue; inserterIds.Add(inserterId); if (includeBranches) { var pickTargetId = inserter.pickTarget; if (pickTargetId > 0) { ref var pickTarget = ref factory.entityPool[pickTargetId]; if (pickTarget.id == pickTargetId && pickTarget.beltId > 0) { ref var pickTargetBelt = ref cargoTraffic.beltPool[pickTarget.beltId]; if (pickTargetBelt.id == pickTargetBelt.segPathId && !pathIds.Contains(pickTargetBelt.segPathId)) { pathIds.Add(pickTargetBelt.segPathId); pendingPathIds.Add(pickTargetBelt.segPathId); } } } var insertTargetId = inserter.insertTarget; if (insertTargetId > 0) { ref var insertTarget = ref factory.entityPool[insertTargetId]; if (insertTarget.id == insertTargetId && insertTarget.beltId > 0) { ref var insertTargetBelt = ref cargoTraffic.beltPool[insertTarget.beltId]; if (insertTargetBelt.id == insertTargetBelt.segPathId && !pathIds.Contains(insertTargetBelt.segPathId)) { pathIds.Add(insertTargetBelt.segPathId); pendingPathIds.Add(insertTargetBelt.segPathId); } } } } } } } if (!includeBranches) continue; foreach (var inputPathId in path.inputPaths) { if (pathIds.Contains(inputPathId)) continue; pathIds.Add(inputPathId); pendingPathIds.Add(inputPathId); } if (path.outputPath == null) continue; var outputPathId = path.outputPath.id; if (pathIds.Contains(outputPathId)) continue; pathIds.Add(outputPathId); pendingPathIds.Add(outputPathId); } var mainPlayer = factory.gameData.mainPlayer; foreach (var pathId in pathIds) { var cargoPath = cargoTraffic.GetCargoPath(pathId); if (cargoPath == null) continue; var end = cargoPath.bufferLength - 5; var buffer = cargoPath.buffer; for (var i = 0; i <= end;) { if (buffer[i] >= 246) { var delta = 250 - buffer[i]; if (delta > 0) i += delta; var index = buffer[i + 1] - 1 + (buffer[i + 2] - 1) * 100 + (buffer[i + 3] - 1) * 10000 + (buffer[i + 4] - 1) * 1000000; ref var cargo = ref cargoPath.cargoContainer.cargoPool[index]; var item = cargo.item; takeOutItems[item] = (takeOutItems.TryGetValue(item, out var value) ? value : 0) + ((long)cargo.stack | ((long)cargo.inc << 32)); Array.Clear(buffer, i - 4, 10); i += 6; if (cargoPath.updateLen < i) cargoPath.updateLen = i; i += 4; cargoPath.cargoContainer.RemoveCargo(index); } else { i += 5; if (i > end && i < end + 5) { i = end; } } } } foreach (var inserterId in inserterIds) { ref var inserter = ref factorySystem.inserterPool[inserterId]; if (inserter.itemId > 0 && inserter.stackCount > 0) { takeOutItems[inserter.itemId] = (takeOutItems.TryGetValue(inserter.itemId, out var value) ? value : 0) + ((long)inserter.itemCount | ((long)inserter.itemInc << 32)); inserter.itemId = 0; inserter.stackCount = 0; inserter.itemCount = 0; inserter.itemInc = 0; } } foreach (var kvp in takeOutItems) { var added = mainPlayer.TryAddItemToPackage(kvp.Key, (int)(kvp.Value & 0xFFFFFFFF), (int)(kvp.Value >> 32), true, entityId); if (added > 0) UIItemup.Up(kvp.Key, added); } } } }