From fb91d68cf8f8e65951511ae9dc883b5b7690f981 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Fri, 10 Apr 2026 13:42:57 +0100 Subject: [PATCH 01/19] base module. This hasnt been tested on 2b so things like the message to pearl might not work until i can properly test and verify it --- .../lambda/mixin/MinecraftClientMixin.java | 7 + .../ClientPlayInteractionManagerMixin.java | 14 + .../com/lambda/config/groups/BuildConfig.kt | 2 +- .../com/lambda/config/groups/BuildSettings.kt | 2 +- .../lambda/config/groups/InventorySettings.kt | 2 +- .../com/lambda/event/events/GuiEvent.kt | 9 +- src/main/kotlin/com/lambda/gui/DearImGui.kt | 4 +- .../lambda/gui/components/ClickGuiLayout.kt | 2 +- .../com/lambda/gui/components/HudGuiLayout.kt | 2 +- .../kotlin/com/lambda/gui/snap/SnapManager.kt | 2 +- .../simulation/checks/InteractSim.kt | 14 +- .../placement/post/PoweredPostProcessor.kt | 38 ++ .../managers/PacketLimitHandler.kt | 4 +- .../managers/rotating/RotationManager.kt | 18 +- .../module/modules/client/AutoUpdater.kt | 2 +- .../lambda/module/modules/world/StashMover.kt | 568 ++++++++++++++++++ .../lambda/task/tasks/OpenContainerTask.kt | 19 +- src/main/kotlin/com/lambda/util/TickTimer.kt | 2 +- 18 files changed, 679 insertions(+), 32 deletions(-) create mode 100644 src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/PoweredPostProcessor.kt create mode 100644 src/main/kotlin/com/lambda/module/modules/world/StashMover.kt diff --git a/src/main/java/com/lambda/mixin/MinecraftClientMixin.java b/src/main/java/com/lambda/mixin/MinecraftClientMixin.java index 702af3fec..309ec2dfb 100644 --- a/src/main/java/com/lambda/mixin/MinecraftClientMixin.java +++ b/src/main/java/com/lambda/mixin/MinecraftClientMixin.java @@ -20,6 +20,7 @@ import com.lambda.core.TimerManager; import com.lambda.event.EventFlow; import com.lambda.event.events.ClientEvent; +import com.lambda.event.events.GuiEvent; import com.lambda.event.events.InventoryEvent; import com.lambda.event.events.TickEvent; import com.lambda.gui.DearImGui; @@ -205,4 +206,10 @@ void updateWindowTitle(CallbackInfo ci) { WindowUtils.setLambdaTitle(); ci.cancel(); } + + @Inject(method = "setScreen", at = @At("HEAD"), cancellable = true) + private void injectSetScreen(Screen screen, CallbackInfo ci) { + var event = new GuiEvent.ScreenOpen(screen); + if (EventFlow.post(event).isCanceled()) ci.cancel(); + } } diff --git a/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java b/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java index 3525d6ce6..6ede77813 100644 --- a/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java +++ b/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java @@ -21,6 +21,8 @@ import com.lambda.event.events.InventoryEvent; import com.lambda.event.events.PlayerEvent; import com.lambda.interaction.managers.inventory.InventoryManager; +import com.lambda.interaction.managers.rotating.RotationManager; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import net.minecraft.client.network.ClientPlayerEntity; @@ -73,6 +75,18 @@ public void interactItemHead(PlayerEntity player, Hand hand, CallbackInfoReturna } } + @ModifyExpressionValue(method = "method_41929", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;getYaw()F")) + private float modifyHand(float original) { + var headYaw = RotationManager.getHeadYaw(); + return headYaw != null ? headYaw : original; + } + + @ModifyExpressionValue(method = "method_41929", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;getPitch()F")) + private float modifySequence(float original) { + var headPitch = RotationManager.getHeadPitch(); + return headPitch != null ? headPitch : original; + } + @Inject(method = "attackBlock", at = @At("HEAD"), cancellable = true) public void onAttackBlock(BlockPos pos, Direction side, CallbackInfoReturnable cir) { if (EventFlow.post(new PlayerEvent.Attack.Block(pos, side)).isCanceled()) { diff --git a/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt b/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt index 99230e868..81c36d0e3 100644 --- a/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt +++ b/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt @@ -36,7 +36,7 @@ interface BuildConfig : ISettingGroup { val maxBuildDependencies: Int val limitTimeframe: Int - val actionPacketLimit: Int + val actionLimit: Int val interactionPacketLimit: Int val blockReach: Double diff --git a/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt b/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt index f01e4bd2f..ab1187b11 100644 --- a/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt @@ -49,7 +49,7 @@ class BuildSettings( override val maxBuildDependencies by c.setting("${prefix}Max Sim Dependencies", 3, 0..10, 1, "Maximum dependency build results", visibility = visibility).group(*baseGroup, Group.General).index() override val limitTimeframe by c.setting("${prefix}Limit Timeframe", 6, 1..30, 1, "The timeframe in which the limit is bound to", "ticks", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() - override val actionPacketLimit by c.setting("${prefix}Action Packet Limit", 55, 1..100, 1, "The maximum allowed action packets to be sent to the server per given timeframe", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() + override val actionLimit by c.setting("${prefix}Action Limit", 50, 1..100, 1, "The maximum allowed action packets to be sent to the server per given timeframe", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() override val interactionPacketLimit by c.setting("Interaction Limit", 9, 1..20, 1, "The maximum allowed interaction packets to be sent to the server per given timeframe", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() override var blockReach by c.setting("${prefix}Interact Reach", 4.5, 1.0..7.0, 0.01, "Maximum block interaction distance", visibility = visibility).group(*baseGroup, Group.Reach).index() diff --git a/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt b/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt index e00c5a228..c71898ba2 100644 --- a/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt @@ -36,7 +36,7 @@ class InventorySettings( Access("Access") } - override val actionsPerSecond by c.setting("${prefix}Actions Per Second", 100, 0..100, 1, "How many inventory actions can be performed per tick", visibility = visibility).group(*baseGroup, Group.General).index() + override val actionsPerSecond by c.setting("${prefix}Actions Per Second", 18, 0..100, 1, "How many inventory actions can be performed per tick", visibility = visibility).group(*baseGroup, Group.General).index() override val tickStageMask by c.setting("${prefix}Inventory Stage Mask", ALL_STAGES.toSet(), description = "The sub-tick timing at which inventory actions are performed", displayClassName = true, visibility = visibility).group(*baseGroup, Group.General).index() override val disposables by c.setting("${prefix}Disposables", ItemUtils.defaultDisposables, description = "Items that will be ignored when checking for a free slot", visibility = visibility).group(*baseGroup, Group.Container).index() override val swapWithDisposables by c.setting("${prefix}Swap With Disposables", true, "Swap items with disposable ones", visibility = visibility).group(*baseGroup, Group.Container).index() diff --git a/src/main/kotlin/com/lambda/event/events/GuiEvent.kt b/src/main/kotlin/com/lambda/event/events/GuiEvent.kt index fa04a2ad6..4cbd037f0 100644 --- a/src/main/kotlin/com/lambda/event/events/GuiEvent.kt +++ b/src/main/kotlin/com/lambda/event/events/GuiEvent.kt @@ -21,13 +21,14 @@ import com.lambda.event.Event import com.lambda.event.callback.Cancellable import com.lambda.event.callback.ICancellable import net.minecraft.block.entity.SignBlockEntity +import net.minecraft.client.gui.screen.Screen sealed class GuiEvent { /** * Triggered when a new ImGui frame is created and the client - * is allowed to submit any command from this point until [EndFrame]. + * is allowed to submit any command from this point until [EndImguiFrame]. */ - data object NewFrame : Event + data object NewImguiFrame : Event /** * Triggered when the previous ImGui frame is ended and the client @@ -35,7 +36,7 @@ sealed class GuiEvent { * * By default, the game's framebuffer is bound. */ - data object EndFrame : Event + data object EndImguiFrame : Event /** * Triggered when the sign editor GUI is opened. Can be canceled. @@ -44,4 +45,6 @@ sealed class GuiEvent { var sign: SignBlockEntity, var front: Boolean ) : ICancellable by Cancellable() + + data class ScreenOpen(val screen: Screen?) : ICancellable by Cancellable() } diff --git a/src/main/kotlin/com/lambda/gui/DearImGui.kt b/src/main/kotlin/com/lambda/gui/DearImGui.kt index 6e3be6e41..57380751d 100644 --- a/src/main/kotlin/com/lambda/gui/DearImGui.kt +++ b/src/main/kotlin/com/lambda/gui/DearImGui.kt @@ -115,9 +115,9 @@ object DearImGui : Loadable { ClickGuiLayout.applyStyle(lastScale) ImGui.newFrame() - GuiEvent.NewFrame.post() + GuiEvent.NewImguiFrame.post() ImGui.render() - GuiEvent.EndFrame.post() + GuiEvent.EndImguiFrame.post() implGl3.renderDrawData(ImGui.getDrawData()) diff --git a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt index 5ca1a40e6..342f5c58b 100644 --- a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt +++ b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt @@ -244,7 +244,7 @@ object ClickGuiLayout : Loadable, Configurable(GuiConfig) { val modalWindowDimBg by setting("Modal Window Dim Background", Color(35, 0, 14, 90)).group(Group.Colors) init { - listen { + listen { if (!open) return@listen buildLayout { diff --git a/src/main/kotlin/com/lambda/gui/components/HudGuiLayout.kt b/src/main/kotlin/com/lambda/gui/components/HudGuiLayout.kt index ead67b23c..c192edb3c 100644 --- a/src/main/kotlin/com/lambda/gui/components/HudGuiLayout.kt +++ b/src/main/kotlin/com/lambda/gui/components/HudGuiLayout.kt @@ -81,7 +81,7 @@ object HudGuiLayout : Loadable, Configurable(HudConfig) { private const val TWO_PI_F = (2f * PI).toFloat() init { - listen { + listen { if (mc.options.hudHidden) return@listen buildLayout { diff --git a/src/main/kotlin/com/lambda/gui/snap/SnapManager.kt b/src/main/kotlin/com/lambda/gui/snap/SnapManager.kt index 60e8bf249..10115bdd9 100644 --- a/src/main/kotlin/com/lambda/gui/snap/SnapManager.kt +++ b/src/main/kotlin/com/lambda/gui/snap/SnapManager.kt @@ -54,7 +54,7 @@ object SnapManager : Loadable { ) init { - listen { + listen { val vp = ImGui.getMainViewport() val io = ImGui.getIO() beginFrame(vp.sizeX, vp.sizeY, io.fontGlobalScale) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/InteractSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/InteractSim.kt index b20d4d348..0dd73ce83 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/InteractSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/InteractSim.kt @@ -47,7 +47,6 @@ import com.lambda.util.item.ItemUtils.blockItem import com.lambda.util.math.MathUtils.floorToInt import com.lambda.util.math.minus import com.lambda.util.player.MovementUtils.sneaking -import com.lambda.util.player.SlotUtils.hotbarStacks import com.lambda.util.player.copyPlayer import com.lambda.util.world.raycast.RayCastUtils.blockResult import kotlinx.coroutines.CoroutineScope @@ -62,7 +61,7 @@ import net.minecraft.entity.Entity import net.minecraft.item.BlockItem import net.minecraft.item.Item import net.minecraft.item.ItemPlacementContext -import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot import net.minecraft.state.property.Properties import net.minecraft.util.Hand import net.minecraft.util.math.BlockPos @@ -142,7 +141,7 @@ class InteractSim private constructor(simInfo: InteractSimInfo) val interactContext = InteractContext( hitResult, rotationRequest { rotation(checkedHit.rotation) }, - getSwapStack(item, supervisorScope)?.inventoryIndex ?: return, + getSwapSlot(item, supervisorScope)?.index ?: return, pos, state, expectedState, @@ -195,7 +194,7 @@ class InteractSim private constructor(simInfo: InteractSimInfo) val interactContext = InteractContext( hitResult, rotationRequest { rotation(rotationRequest) }, - getSwapStack(item, supervisorScope)?.inventoryIndex ?: return, + getSwapSlot(item, supervisorScope)?.index ?: return, pos, state, rotatePlaceTest.resultState, @@ -211,7 +210,7 @@ class InteractSim private constructor(simInfo: InteractSimInfo) return } - private fun AutomatedSafeContext.getSwapStack(item: Item?, supervisorScope: CoroutineScope): ItemStack? { + private fun AutomatedSafeContext.getSwapSlot(item: Item?, supervisorScope: CoroutineScope): Slot? { if (item?.isEnabled(world.enabledFeatures) == false) { result(InteractResult.BlockFeatureDisabled(pos, item)) supervisorScope.cancel() @@ -224,9 +223,8 @@ class InteractSim private constructor(simInfo: InteractSimInfo) result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) return null } - val hotbarStacks = player.hotbarStacks - return stackSelection.filterStacks(container.stacks).run { - firstOrNull { hotbarStacks.indexOf(it) == player.inventory.selectedSlot } + return stackSelection.filterSlots(container.slots).run { + firstOrNull { it.index == player.inventory.selectedSlot } ?: firstOrNull() } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/PoweredPostProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/PoweredPostProcessor.kt new file mode 100644 index 000000000..69e9048c8 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/PoweredPostProcessor.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2026 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.simulation.processing.preprocessors.property.placement.post + +import com.lambda.context.SafeContext +import com.lambda.interaction.construction.simulation.processing.PreProcessingInfoAccumulator +import com.lambda.interaction.construction.simulation.processing.PropertyPostProcessor +import net.minecraft.block.BlockState +import net.minecraft.block.ButtonBlock +import net.minecraft.block.LeverBlock +import net.minecraft.state.property.Properties +import net.minecraft.util.math.BlockPos + +// Collected using reflections and then accessed from a collection in ProcessorRegistry +@Suppress("unused") +object PoweredPostProcessor : PropertyPostProcessor { + override fun acceptsState(state: BlockState, targetState: BlockState) = + state.block is ButtonBlock || state.block is LeverBlock && state.get(Properties.POWERED) != targetState.get(Properties.POWERED) + + context(safeContext: SafeContext) + override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) = + with(StandardInteractPostProcessor) { preProcess(state, targetState, pos) } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/PacketLimitHandler.kt b/src/main/kotlin/com/lambda/interaction/managers/PacketLimitHandler.kt index 6c9567ba3..9190a39da 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/PacketLimitHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/PacketLimitHandler.kt @@ -27,7 +27,7 @@ object PacketLimitHandler { private val packetLimitMap = PacketType.entries.associateWith { LimitHandler(0, 0, 0) } init { - listen(priority = { Int.MIN_VALUE }) { + listen(priority = { Int.MAX_VALUE }) { packetLimitMap.values.forEach { limitHandler -> with(limitHandler) { tickTimer.tick() @@ -60,6 +60,6 @@ object PacketLimitHandler { } enum class PacketType(val maxPacketsPerTimeframe: BuildConfig.() -> Int) { - PlayerAction({ actionPacketLimit }), + PlayerAction({ actionLimit }), Interaction({ interactionPacketLimit }) } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationManager.kt b/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationManager.kt index 206b1ae56..ddb15812f 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationManager.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationManager.kt @@ -76,6 +76,8 @@ object RotationManager : Manager( private val IRotationRequest.overridable get() = age >= 1 + private var pauseVanillaOverrides = false + override fun load(): String { super.load() @@ -121,11 +123,11 @@ object RotationManager : Manager( } // Override user interactions with max priority - listen({ Int.MAX_VALUE }) { activeRotation = player.rotation } - listen({ Int.MAX_VALUE }) { activeRotation = player.rotation } - listen({ Int.MAX_VALUE }) { activeRotation = player.rotation } - listen({ Int.MAX_VALUE }) { activeRotation = player.rotation } - listen({ Int.MAX_VALUE }) { activeRotation = player.rotation } + listen({ Int.MAX_VALUE }) { if (!pauseVanillaOverrides) activeRotation = player.rotation } + listen({ Int.MAX_VALUE }) { if (!pauseVanillaOverrides) activeRotation = player.rotation } + listen({ Int.MAX_VALUE }) { if (!pauseVanillaOverrides) activeRotation = player.rotation } + listen({ Int.MAX_VALUE }) { if (!pauseVanillaOverrides) activeRotation = player.rotation } + listen({ Int.MAX_VALUE }) { if (!pauseVanillaOverrides) activeRotation = player.rotation } return "Loaded Rotation Manager" } @@ -179,6 +181,12 @@ object RotationManager : Manager( setPlayerPitch(rotation.pitch) } + fun withoutVanillaOverrides(block: () -> Unit) { + pauseVanillaOverrides = true + block() + pauseVanillaOverrides = false + } + /** * If the rotation has not been changed this tick, the [activeRequest]'s target rotation is updated, and * likewise the [activeRotation]. The [activeRequest] is then updated, ticking the [RotationRequest.keepTicks] diff --git a/src/main/kotlin/com/lambda/module/modules/client/AutoUpdater.kt b/src/main/kotlin/com/lambda/module/modules/client/AutoUpdater.kt index 71458d0ca..e31076fc3 100644 --- a/src/main/kotlin/com/lambda/module/modules/client/AutoUpdater.kt +++ b/src/main/kotlin/com/lambda/module/modules/client/AutoUpdater.kt @@ -83,7 +83,7 @@ object AutoUpdater : Module( showInstallModal = false } - listen(alwaysListen = true) { + listen(alwaysListen = true) { initializeFirstLaunchStateIfNeeded() if (showFirstLaunchModal) { diff --git a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt new file mode 100644 index 000000000..86a4327b3 --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -0,0 +1,568 @@ +/* + * Copyright 2026 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.world + +import baritone.api.pathing.goals.GoalBlock +import com.lambda.Lambda.mc +import com.lambda.config.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.applyEdits +import com.lambda.config.settings.complex.Bind +import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress +import com.lambda.context.SafeContext +import com.lambda.event.events.ButtonEvent +import com.lambda.event.events.ChatEvent +import com.lambda.event.events.GuiEvent +import com.lambda.event.events.PacketEvent +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe +import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer +import com.lambda.interaction.BaritoneManager +import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.managers.hotbar.HotbarRequest +import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest +import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.task.RootTask.run +import com.lambda.task.Task +import com.lambda.task.tasks.BuildTask.Companion.build +import com.lambda.task.tasks.OpenContainerTask +import com.lambda.threading.runSafeAutomated +import com.lambda.util.BlockUtils.blockEntity +import com.lambda.util.BlockUtils.blockState +import com.lambda.util.Communication.warn +import com.lambda.util.NamedEnum +import com.lambda.util.TickTimer +import com.lambda.util.extension.containerSlots +import com.lambda.util.extension.containerStacks +import com.lambda.util.extension.playerSlots +import com.lambda.util.extension.rotation +import com.lambda.util.math.distSq +import com.lambda.util.math.setAlpha +import com.lambda.util.player.SlotUtils.allSlots +import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks +import com.lambda.util.player.SlotUtils.hotbarSlots +import com.lambda.util.world.raycast.RayCastUtils.blockResult +import net.minecraft.block.ButtonBlock +import net.minecraft.block.entity.LootableContainerBlockEntity +import net.minecraft.client.gui.screen.DeathScreen +import net.minecraft.item.Items +import net.minecraft.network.packet.c2s.play.ClientStatusC2SPacket +import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket +import net.minecraft.network.packet.s2c.play.PlayerRespawnS2CPacket +import net.minecraft.state.property.Properties +import net.minecraft.util.Hand +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Box +import org.lwjgl.glfw.GLFW +import java.awt.Color +import kotlin.to + +object StashMover : Module( + name = "StashMover", + description = "Moves items from one stash location to another", + tag = ModuleTag.WORLD +) { + private enum class Role(val createTask: () -> Task<*>) { + MoverBot({ MoverBot() }), + PearlBot({ PearlBot() }) + } + + private enum class Group(override val displayName: String) : NamedEnum { + General("General"), + CommandBinds("Command Binds"), + Render("Render") + } + + private val role: Role by setting("Role", Role.MoverBot).group(Group.General) + .onValueChange { _, to -> + if (to == Role.PearlBot) { + chestPullSelMode = false + chestPutSelMode = false + sel1 = null + sel2 = null + } + } + private val pearlBotName by setting("PearlBot Name", "Steve") { role == Role.MoverBot }.group(Group.General) + private val moverBotName by setting("MoverBot Name", "Steve") { role == Role.PearlBot }.group(Group.General) + private var chestPullSelMode: Boolean by setting("Chest Pull Sel Mode", false, "Enables the mode to select the stash containers you want to move items from") { role == Role.MoverBot }.group(Group.General) + .onValueChange { _, to -> if (to) chestPutSelMode = false } + private var chestPutSelMode: Boolean by setting("Chest Put Sel Mode", false, "Enables the mod to select the stash containers you want to move items into") { role == Role.MoverBot }.group(Group.General) + .onValueChange { _, to -> if (to) chestPullSelMode = false } + private val pearlMsgTimeout by setting("Pearl Msg Timeout", 200, 0..1500, 1, "Ticks before messaging the pearl bot again", "ticks") { role == Role.MoverBot }.group(Group.General) + private val pearlButtonTimeout by setting("Pearl Button Timeout", 200, 0..1500, 1, "Ticks before pressing the pearl dispenser button again", "ticks") { role == Role.MoverBot }.group(Group.General) + private val killRespawnTimeout by setting("Kill/Respawn Timeout", 200, 0..1500, 1, "Ticks before sending the kill command or attempting to respawn again", "ticks") { role == Role.MoverBot }.group(Group.General) + private val actionDelay by setting("Action Delay", 3, 0..20, 1, "The delay after performing one action, before the next") { role == Role.MoverBot }.group(Group.General) + private val startStop by setting("Start/Stop", Bind.EMPTY, "Starts and stops the selected role").group(Group.General) + .onPress { event -> + event.cancel() + chestPullSelMode = false + chestPutSelMode = false + task?.let { runningTask -> + runningTask.cancel() + task = null + return@onPress + } + task = role.createTask().run() + } + + private val indexSelectedContainers by setting("Index Selected Containers", Bind.EMPTY, "Indexes the selected containers to pull/push items from/to") { role == Role.MoverBot }.group(Group.CommandBinds) + .onPress { event -> + event.cancel() + consumeSelection { pos -> + if (blockEntity(pos) !is LootableContainerBlockEntity) return@consumeSelection + if (chestPullSelMode) pullContainers.add(pos) + else if (chestPutSelMode) putContainers.add(pos) + } + } + private val removeSelectedContainers by setting("Remove Selected Containers", Bind.EMPTY, "Removes the selected containers from being pull/pushed from/to") { role == Role.MoverBot }.group(Group.CommandBinds) + .onPress { event -> + event.cancel() + consumeSelection { pos -> + if (blockEntity(pos) !is LootableContainerBlockEntity) return@consumeSelection + if (chestPullSelMode) pullContainers.remove(pos) + else if (chestPutSelMode) putContainers.remove(pos) + } + } + private val setPearlButtonPos by setting("Set Pearl Button Pos", Bind.EMPTY, "Sets the button used to dispense a pearl for the player") { role == Role.MoverBot }.group(Group.CommandBinds) + .onPress { event -> + event.cancel() + val pos = mc.crosshairTarget?.blockResult?.blockPos ?: return@onPress + if (blockState(pos).block !is ButtonBlock) { + warn("Given position does not contain a button!") + return@onPress + } + pearlDispensePos = pos + } + private val setPearlThrowPosAndRotation by setting("Set Pearl Throw", Bind.EMPTY, "Sets the pearl throw position and rotation. (This is best if you throw somewhat sideways into a line of bubble columns)") { role == Role.MoverBot }.group(Group.CommandBinds) + .onPress { event -> + event.cancel() + pearlThrowPos = player.blockPos + pearlRotation = player.rotation + } + private val setPearlBotButton by setting("Set PearlBot Button", Bind.EMPTY, "Sets the button position for the pearl bot to press to load the mover bot") { role == Role.PearlBot }.group(Group.CommandBinds) + .onPress { event -> + event.cancel() + val pos = mc.crosshairTarget?.blockResult?.blockPos ?: return@onPress + if (blockState(pos).block !is ButtonBlock) { + warn("Given position does not contain a button!") + return@onPress + } + pearlBotButton = pos + } + + private var sel1: BlockPos? = null + private var sel2: BlockPos? = null + + private val pullContainers = hashSetOf() + private val pulledContainers = hashSetOf() + private val putContainers = hashSetOf() + private val filledContainers = hashSetOf() + + private var pearlDispensePos: BlockPos? = null + private var pearlThrowPos: BlockPos? = null + private var pearlRotation: Rotation? = null + + private var pearlBotButton: BlockPos? = null + + private var task: Task<*>? = null + + init { + setModulePriority(100) + setDefaultAutomationConfig { + applyEdits { + hotbarConfig::tickStageMask.edit { defaultValue(mutableSetOf(TickEvent.Pre)) } + } + } + + listen { event -> + if (!chestPullSelMode && !chestPutSelMode) return@listen + if (event.action != GLFW.GLFW_PRESS || mc.currentScreen != null) return@listen + + if (event.button == 0) { + event.cancel() + sel1 = mc.crosshairTarget?.blockResult?.blockPos ?: return@listen + } + else if (event.button == 1) { + event.cancel() + sel2 = mc.crosshairTarget?.blockResult?.blockPos ?: return@listen + } + } + + onDisable { + task?.cancel() + task = null + sel1 = null + sel2 = null + pullContainers.clear() + pulledContainers.clear() + putContainers.clear() + filledContainers.clear() + pearlDispensePos = null + pearlThrowPos = null + pearlRotation = null + pearlBotButton = null + } + + immediateRenderer("StashMover Immediate Renderer") { + if (!chestPullSelMode && !chestPutSelMode) return@immediateRenderer + sel1?.let { sel1 -> + sel2?.let { sel2 -> + val box = Box(sel1).union(Box(sel2)) + box(box) { + val color = if (chestPutSelMode) Color.GREEN else Color.BLUE + colors(color.setAlpha(0.1), color) + } + } + } + } + } + + private fun consumeSelection(callback: (pos: BlockPos) -> Unit) { + sel1?.let { sel1 -> + sel2?.let { sel2 -> + val minX = minOf(sel1.x, sel2.x) + val maxX = maxOf(sel1.x, sel2.x) + val minY = minOf(sel1.y, sel2.y) + val maxY = maxOf(sel1.y, sel2.y) + val minZ = minOf(sel1.z, sel2.z) + val maxZ = maxOf(sel1.z, sel2.z) + (minX..maxX).forEach { x -> + (minZ..maxZ).forEach { z -> + (minY..maxY).forEach { y -> + callback.invoke(BlockPos(x, y, z)) + } + } + } + } + } + sel1 = null + sel2 = null + } + + private class MoverBot : Task() { + override val name + get() = "Moving a stash, pearled by $pearlBotName, current state: $moverState" + private var moverState = MoverState.TakingItems + set(newValue) { + actionDelayTimer.reset() + delayingNextAction = true + field = newValue + } + + private var actionDelayTimer = TickTimer() + private var delayingNextAction = false + + private var pullContainer: BlockPos? = null + private var tickTimer = TickTimer() + + private var putContainer: BlockPos? = null + + init { + listen { + if (delayingNextAction) { + actionDelayTimer.tick() + if (!actionDelayTimer.hasSurpassed(actionDelay)) return@listen + delayingNextAction = false + } + + when (moverState) { + MoverState.OpeningPullContainer -> { + val target = pullContainers.minByOrNull { it distSq player.blockPos } + ?: run { + success("Pull containers exhausted!") + return@listen + } + + OpenContainerTask( + target, + StashMover + ).finally { + pullContainer = target + moverState = MoverState.TakingItems + }.execute(this@MoverBot) + } + + MoverState.TakingItems -> { + val screenHandler = player.currentScreenHandler + if (screenHandler === player.playerScreenHandler) { + moverState = MoverState.OpeningPullContainer + return@listen + } + val pullSlots = screenHandler.containerSlots.filter { !it.stack.isEmpty } + if (player.hotbarAndInventoryStacks.any { it.isEmpty } && pullSlots.isNotEmpty()) { + val request = inventoryRequest(settleForLess = true) { + pullSlots.forEach { slot -> + quickMove(slot.id) + } + }.submit() + if (!request.done) return@listen + } + player.closeScreen() + pullContainer?.let { container -> + if (screenHandler.containerStacks.all { it.isEmpty }) { + pullContainers.remove(container) + pulledContainers.add(container) + if (player.hotbarAndInventoryStacks.any { it.isEmpty }) { + moverState = MoverState.OpeningPullContainer + return@listen + } + } + } + moverState = MoverState.MessagingForPearl + } + + MoverState.MessagingForPearl -> { + tickTimer.reset() + connection.sendChatCommand("msg $pearlBotName ${Math.random() * Double.MAX_VALUE}") + moverState = MoverState.AwaitingTeleport + } + + MoverState.AwaitingTeleport -> { + tickTimer.tick() + if (tickTimer.hasSurpassed(pearlMsgTimeout)) + moverState = MoverState.MessagingForPearl + } + + MoverState.OpeningPutContainer -> { + val target = putContainers.minByOrNull { it distSq player.blockPos } + ?: run { + success("Put containers are full!") + return@listen + } + + OpenContainerTask( + target, + StashMover + ).finally { + putContainer = target + moverState = MoverState.PuttingItems + }.execute(this@MoverBot) + } + + MoverState.PuttingItems -> { + val screenHandler = player.currentScreenHandler + if (screenHandler === player.playerScreenHandler) { + moverState = MoverState.OpeningPutContainer + return@listen + } + val putSlots = screenHandler.playerSlots.filter { !it.stack.isEmpty } + if (screenHandler.containerStacks.any { it.isEmpty } && putSlots.isNotEmpty()) { + val request = inventoryRequest(settleForLess = true) { + putSlots.forEach { slot -> + quickMove(slot.id) + } + }.submit() + if (!request.done) return@listen + } + player.closeScreen() + putContainer?.let { container -> + if (screenHandler.containerStacks.all { !it.isEmpty }) { + putContainers.remove(container) + filledContainers.add(container) + if (player.hotbarAndInventoryStacks.any { !it.isEmpty }) { + moverState = MoverState.OpeningPutContainer + return@listen + } + } + } + moverState = MoverState.DispensingPearl + } + + MoverState.DispensingPearl -> { + val dispensePos = pearlDispensePos ?: run { failure("No pearl button set!"); return@listen } + if (player.blockPos != dispensePos) { + BaritoneManager.setGoalAndPath(GoalBlock(dispensePos)) + return@listen + } + if (BaritoneManager.isActive) return@listen + getButtonPressTask(dispensePos) + ?.finally { + tickTimer.reset() + moverState = MoverState.AwaitingPearl + } + ?.execute(this@MoverBot) + } + + MoverState.AwaitingPearl -> { + tickTimer.tick() + if (player.hotbarAndInventoryStacks.any { it.item === Items.ENDER_PEARL }) { + moverState = MoverState.ThrowingPearl + return@listen + } + if (tickTimer.hasSurpassed(pearlButtonTimeout)) + moverState = MoverState.DispensingPearl + } + + MoverState.ThrowingPearl -> { + val throwPos = pearlThrowPos ?: run { failure("No pearl throw pos set!"); return@listen } + if (player.blockPos != throwPos) { + BaritoneManager.setGoalAndPath(GoalBlock(throwPos)) + return@listen + } + if (BaritoneManager.isActive) return@listen + val rotation = pearlRotation ?: run { failure("No pearl rotation set!"); return@listen } + val rotationRequest = rotationRequest { + rotation(rotation) + }.submit() + if (!rotationRequest.done) return@listen + while (player.mainHandStack.item != Items.ENDER_PEARL) { + val hotbarSlot = player.hotbarSlots.firstOrNull { it.stack.item === Items.ENDER_PEARL } + if (hotbarSlot != null) { + val hotbarRequest = HotbarRequest(hotbarSlot.index, StashMover).submit() + if (!hotbarRequest.done) return@listen + break + } else { + val inventorySlot = player.allSlots.firstOrNull { it.stack.item === Items.ENDER_PEARL } + if (inventorySlot == null) { + failure("No pearl in inventory!") + return@listen + } + val inventoryRequest = inventoryRequest { + swap(inventorySlot.id, 0) + }.submit() + if (!inventoryRequest.done) return@listen + } + } + RotationManager.withoutVanillaOverrides { + interaction.interactItem(player, Hand.MAIN_HAND) + } + moverState = MoverState.Killing + } + + MoverState.Killing -> { + tickTimer.reset() + connection.sendChatCommand("kill") + moverState = MoverState.AwaitingDeath + } + + else -> {} + } + } + + listenUnsafe { + when (moverState) { + MoverState.AwaitingDeath -> { + tickTimer.tick() + if (tickTimer.hasSurpassed(killRespawnTimeout)) + moverState = MoverState.Killing + } + + MoverState.Respawning -> { + tickTimer.reset() + mc.networkHandler?.sendPacket(ClientStatusC2SPacket(ClientStatusC2SPacket.Mode.PERFORM_RESPAWN)) + moverState = MoverState.AwaitingRespawn + } + + MoverState.AwaitingRespawn -> { + tickTimer.tick() + if (tickTimer.hasSurpassed(killRespawnTimeout)) + moverState = MoverState.Respawning + } + + else -> {} + } + } + + listenUnsafe { event -> + if (moverState != MoverState.AwaitingRespawn) return@listenUnsafe + if (event.packet !is PlayerRespawnS2CPacket) return@listenUnsafe + moverState = MoverState.OpeningPullContainer + } + + listen { event -> + if (moverState != MoverState.AwaitingTeleport) return@listen + val packet = event.packet + if (packet !is PlayerPositionLookS2CPacket) return@listen + moverState = MoverState.OpeningPutContainer + } + + listen { event -> + if (moverState != MoverState.AwaitingDeath) return@listen + if (event.screen !is DeathScreen) return@listen + moverState = MoverState.Respawning + } + } + + private enum class MoverState { + OpeningPullContainer, + TakingItems, + MessagingForPearl, + AwaitingTeleport, + OpeningPutContainer, + PuttingItems, + DispensingPearl, + AwaitingPearl, + ThrowingPearl, + Killing, + AwaitingDeath, + Respawning, + AwaitingRespawn + } + } + + private class PearlBot : Task() { + override val name + get() = "Pearling $moverBotName for stash moving, current state: $pearlState" + var pearlState = PearlState.Waiting + + init { + listen { + when (pearlState) { + PearlState.Pressing -> { + val buttonPos = pearlBotButton ?: run { failure("No pearl bot button set!"); return@listen } + getButtonPressTask(buttonPos) + ?.finally { + pearlState = PearlState.Waiting + } + ?.execute(this@PearlBot) + } + else -> {} + } + } + + listen { event -> + if (!event.message.string.startsWith("$moverBotName whispers")) return@listen + pearlState = PearlState.Pressing + } + } + + private enum class PearlState { + Waiting, + Pressing + } + } + + context(safeContext: SafeContext) + private fun Task.getButtonPressTask(pos: BlockPos): Task<*>? = + with (safeContext) { + val buttonState = blockState(pos) + if (buttonState.block !is ButtonBlock) { + failure("Pearl button position does not contain a button!") + } + return if (!buttonState.get(Properties.POWERED)) { + StashMover.runSafeAutomated { + mapOf(pos to TargetState.State(buttonState.with(Properties.POWERED, true))) + .build() + } + } else null + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt b/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt index 2aa6d3961..8d5fbf5a8 100644 --- a/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt @@ -27,6 +27,7 @@ import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotat import com.lambda.interaction.managers.rotating.visibilty.lookAtBlock import com.lambda.task.Task import com.lambda.threading.runSafeAutomated +import com.lambda.util.TickTimer import com.lambda.util.world.raycast.RayCastUtils.blockResult import net.minecraft.screen.ScreenHandler import net.minecraft.util.Hand @@ -39,18 +40,19 @@ class OpenContainerTask @Ta5kBuilder constructor( private val waitForSlotLoad: Boolean = true, private val sides: Set = Direction.entries.toSet() ) : Task(), Automated by automated { - override val name get() = "${containerState.description(inScope)} at ${blockPos.toShortString()}" + override val name get() = "${containerState.description()} at ${blockPos.toShortString()}" private var screenHandler: ScreenHandler? = null private var containerState = State.Scoping - private var inScope = 0 + + private val retryTimer = TickTimer() enum class State { Pathing, Scoping, Opening, SlotLoading; - fun description(inScope: Int) = when (this) { + fun description() = when (this) { Pathing -> "Pathing closer" - Scoping -> "Waiting for scope ($inScope)" + Scoping -> "Waiting for scope" Opening -> "Opening container" SlotLoading -> "Waiting for slots to load" } @@ -82,6 +84,15 @@ class OpenContainerTask @Ta5kBuilder constructor( } listen { + if (containerState == State.Opening) { + retryTimer.tick() + if (retryTimer.hasSurpassed(10)) { + retryTimer.reset() + containerState = State.Scoping + } + return@listen + } + if (containerState != State.Scoping && containerState != State.Pathing) return@listen val checkedHit = runSafeAutomated { lookAtBlock(blockPos, sides) } diff --git a/src/main/kotlin/com/lambda/util/TickTimer.kt b/src/main/kotlin/com/lambda/util/TickTimer.kt index b77f61be7..ce9356668 100644 --- a/src/main/kotlin/com/lambda/util/TickTimer.kt +++ b/src/main/kotlin/com/lambda/util/TickTimer.kt @@ -24,7 +24,7 @@ class TickTimer { ticks++ } - fun hasSurpassed(ticks: Int) = this.ticks > ticks + fun hasSurpassed(ticks: Int) = this.ticks >= ticks fun reset() { ticks = 0 From 82923d87e865cca43dfc8996a8745cf813e4d8c3 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:52:34 +0100 Subject: [PATCH 02/19] cleanup/fixes --- .../command/commands/StashMoverCommand.kt | 52 ++ .../managers/inventory/InventoryManager.kt | 4 +- .../container/containers/ChestContainer.kt | 2 +- .../lambda/module/modules/world/StashMover.kt | 471 ++++++++++-------- 4 files changed, 324 insertions(+), 205 deletions(-) create mode 100644 src/main/kotlin/com/lambda/command/commands/StashMoverCommand.kt diff --git a/src/main/kotlin/com/lambda/command/commands/StashMoverCommand.kt b/src/main/kotlin/com/lambda/command/commands/StashMoverCommand.kt new file mode 100644 index 000000000..558a56e93 --- /dev/null +++ b/src/main/kotlin/com/lambda/command/commands/StashMoverCommand.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2026 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.command.commands + +import com.lambda.brigadier.argument.literal +import com.lambda.brigadier.execute +import com.lambda.brigadier.required +import com.lambda.command.LambdaCommand +import com.lambda.module.modules.world.StashMover +import com.lambda.threading.runSafe +import com.lambda.util.extension.CommandBuilder + +object StashMoverCommand : LambdaCommand( + name = "stashmover", + description = "Set configurations for the StashMover module" +) { + override fun CommandBuilder.create() { + required(literal("index_selected_containers")) { + execute { runSafe { StashMover.indexSelectedContainers() } } + } + required(literal("remove_selected_containers")) { + execute { runSafe { StashMover.removeSelectedContainers() } } + } + required(literal("set_pearl_button_pos")) { + execute { runSafe { StashMover.setPearlButtonPos() } } + } + required(literal("set_pearl_throw")) { + execute { runSafe { StashMover.setPearlThrow() } } + } + required(literal("set_pearlbot_button")) { + execute { runSafe { StashMover.setPearlBotButton() } } + } + required(literal("start-stop")) { + execute { runSafe { StashMover.startStop() } } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt index e116793c4..e9ee30315 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt @@ -234,7 +234,7 @@ object InventoryManager : Manager( val alteredSlots = if (packet.syncId == 0) alteredPlayerSlots else alteredSlots val matches = alteredSlots.removeIf { - it.syncId == packet.slot && it.after.equal(itemStack) + it.slotId == packet.slot && it.after.equal(itemStack) } if (packet.syncId == 0) { @@ -267,7 +267,7 @@ object InventoryManager : Manager( } private data class InventoryChange( - val syncId: Int, + val slotId: Int, val before: ItemStack, val after: ItemStack ) diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt index 3c6543b71..18cc96534 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt @@ -61,7 +61,7 @@ data class ChestContainer( override fun accessThen(exitAfter: Boolean, taskGenerator: TaskGenerator): Task<*> = OpenContainerTask(blockPos, automatedSafeContext).then { taskGenerator.invoke(automatedSafeContext, Unit).finally { - if (exitAfter) automatedSafeContext.player.closeScreen() + if (exitAfter) automatedSafeContext.player.closeHandledScreen() } } } diff --git a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt index 86a4327b3..3d9adf9fb 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -35,6 +35,7 @@ import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRend import com.lambda.interaction.BaritoneManager import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.managers.hotbar.HotbarRequest +import com.lambda.interaction.managers.interacting.InteractConfig import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.Rotation @@ -48,6 +49,7 @@ import com.lambda.task.tasks.OpenContainerTask import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockEntity import com.lambda.util.BlockUtils.blockState +import com.lambda.util.Communication.info import com.lambda.util.Communication.warn import com.lambda.util.NamedEnum import com.lambda.util.TickTimer @@ -74,6 +76,7 @@ import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Box import org.lwjgl.glfw.GLFW import java.awt.Color +import kotlin.run import kotlin.to object StashMover : Module( @@ -88,8 +91,7 @@ object StashMover : Module( private enum class Group(override val displayName: String) : NamedEnum { General("General"), - CommandBinds("Command Binds"), - Render("Render") + CommandBinds("Command Binds") } private val role: Role by setting("Role", Role.MoverBot).group(Group.General) @@ -104,69 +106,43 @@ object StashMover : Module( private val pearlBotName by setting("PearlBot Name", "Steve") { role == Role.MoverBot }.group(Group.General) private val moverBotName by setting("MoverBot Name", "Steve") { role == Role.PearlBot }.group(Group.General) private var chestPullSelMode: Boolean by setting("Chest Pull Sel Mode", false, "Enables the mode to select the stash containers you want to move items from") { role == Role.MoverBot }.group(Group.General) - .onValueChange { _, to -> if (to) chestPutSelMode = false } + .onValueChange { _, to -> if (to) { chestPutSelMode = false; StashMover.info("Enabled chest pull selection mode!") } } private var chestPutSelMode: Boolean by setting("Chest Put Sel Mode", false, "Enables the mod to select the stash containers you want to move items into") { role == Role.MoverBot }.group(Group.General) - .onValueChange { _, to -> if (to) chestPullSelMode = false } - private val pearlMsgTimeout by setting("Pearl Msg Timeout", 200, 0..1500, 1, "Ticks before messaging the pearl bot again", "ticks") { role == Role.MoverBot }.group(Group.General) - private val pearlButtonTimeout by setting("Pearl Button Timeout", 200, 0..1500, 1, "Ticks before pressing the pearl dispenser button again", "ticks") { role == Role.MoverBot }.group(Group.General) - private val killRespawnTimeout by setting("Kill/Respawn Timeout", 200, 0..1500, 1, "Ticks before sending the kill command or attempting to respawn again", "ticks") { role == Role.MoverBot }.group(Group.General) + .onValueChange { _, to -> if (to) { chestPullSelMode = false; StashMover.info("Enabled chest put selection mode!") } } + private val pearlMsgTimeout by setting("Pearl Msg Timeout", 100, 0..1500, 1, "Ticks before messaging the pearl bot again", "ticks") { role == Role.MoverBot }.group(Group.General) + private val pearlButtonTimeout by setting("Pearl Button Timeout", 100, 0..1500, 1, "Ticks before pressing the pearl dispenser button again", "ticks") { role == Role.MoverBot }.group(Group.General) + private val killRespawnTimeout by setting("Kill/Respawn Timeout", 100, 0..1500, 1, "Ticks before sending the kill command or attempting to respawn again", "ticks") { role == Role.MoverBot }.group(Group.General) private val actionDelay by setting("Action Delay", 3, 0..20, 1, "The delay after performing one action, before the next") { role == Role.MoverBot }.group(Group.General) private val startStop by setting("Start/Stop", Bind.EMPTY, "Starts and stops the selected role").group(Group.General) .onPress { event -> event.cancel() - chestPullSelMode = false - chestPutSelMode = false - task?.let { runningTask -> - runningTask.cancel() - task = null - return@onPress - } - task = role.createTask().run() + startStop() } private val indexSelectedContainers by setting("Index Selected Containers", Bind.EMPTY, "Indexes the selected containers to pull/push items from/to") { role == Role.MoverBot }.group(Group.CommandBinds) .onPress { event -> event.cancel() - consumeSelection { pos -> - if (blockEntity(pos) !is LootableContainerBlockEntity) return@consumeSelection - if (chestPullSelMode) pullContainers.add(pos) - else if (chestPutSelMode) putContainers.add(pos) - } + indexSelectedContainers() } private val removeSelectedContainers by setting("Remove Selected Containers", Bind.EMPTY, "Removes the selected containers from being pull/pushed from/to") { role == Role.MoverBot }.group(Group.CommandBinds) .onPress { event -> event.cancel() - consumeSelection { pos -> - if (blockEntity(pos) !is LootableContainerBlockEntity) return@consumeSelection - if (chestPullSelMode) pullContainers.remove(pos) - else if (chestPutSelMode) putContainers.remove(pos) - } + removeSelectedContainers() } private val setPearlButtonPos by setting("Set Pearl Button Pos", Bind.EMPTY, "Sets the button used to dispense a pearl for the player") { role == Role.MoverBot }.group(Group.CommandBinds) .onPress { event -> event.cancel() - val pos = mc.crosshairTarget?.blockResult?.blockPos ?: return@onPress - if (blockState(pos).block !is ButtonBlock) { - warn("Given position does not contain a button!") - return@onPress - } - pearlDispensePos = pos + setPearlButtonPos() } private val setPearlThrowPosAndRotation by setting("Set Pearl Throw", Bind.EMPTY, "Sets the pearl throw position and rotation. (This is best if you throw somewhat sideways into a line of bubble columns)") { role == Role.MoverBot }.group(Group.CommandBinds) .onPress { event -> event.cancel() - pearlThrowPos = player.blockPos - pearlRotation = player.rotation + setPearlThrow() } private val setPearlBotButton by setting("Set PearlBot Button", Bind.EMPTY, "Sets the button position for the pearl bot to press to load the mover bot") { role == Role.PearlBot }.group(Group.CommandBinds) .onPress { event -> event.cancel() - val pos = mc.crosshairTarget?.blockResult?.blockPos ?: return@onPress - if (blockState(pos).block !is ButtonBlock) { - warn("Given position does not contain a button!") - return@onPress - } - pearlBotButton = pos + setPearlBotButton() } private var sel1: BlockPos? = null @@ -189,7 +165,8 @@ object StashMover : Module( setModulePriority(100) setDefaultAutomationConfig { applyEdits { - hotbarConfig::tickStageMask.edit { defaultValue(mutableSetOf(TickEvent.Pre)) } + buildConfig::checkSideVisibility.edit { defaultValue(true) } + interactConfig::airPlace.edit { defaultValue(InteractConfig.AirPlaceMode.None) } } } @@ -224,6 +201,16 @@ object StashMover : Module( immediateRenderer("StashMover Immediate Renderer") { if (!chestPullSelMode && !chestPutSelMode) return@immediateRenderer + sel1?.let { sel1 -> + box(Box(sel1)) { + colors(Color.PINK.setAlpha(0.1), Color.PINK) + } + } + sel2?.let { sel2 -> + box(Box(sel2)) { + colors(Color.MAGENTA.setAlpha(0.1), Color.MAGENTA) + } + } sel1?.let { sel1 -> sel2?.let { sel2 -> val box = Box(sel1).union(Box(sel2)) @@ -236,6 +223,80 @@ object StashMover : Module( } } + context(safeContext: SafeContext) + fun indexSelectedContainers() { + var addCount = 0 + consumeSelection { pos -> + if (safeContext.blockEntity(pos) !is LootableContainerBlockEntity) return@consumeSelection + addCount++ + pulledContainers.remove(pos) + filledContainers.remove(pos) + if (chestPullSelMode) { + putContainers.remove(pos) + pullContainers.add(pos) + } + else if (chestPutSelMode) { + pullContainers.remove(pos) + putContainers.add(pos) + } + } + StashMover.info("Indexed $addCount ${if (chestPullSelMode) "pull" else "put"} containers!") + } + + context(safeContext: SafeContext) + fun removeSelectedContainers() { + var removeCount = 0 + consumeSelection { pos -> + if (safeContext.blockEntity(pos) !is LootableContainerBlockEntity) return@consumeSelection + if (pullContainers.remove(pos) || + pulledContainers.remove(pos) || + putContainers.remove(pos) || + filledContainers.remove(pos)) removeCount++ + } + StashMover.info("Removed $removeCount containers!") + } + + context(safeContext: SafeContext) + fun setPearlButtonPos() { + val pos = mc.crosshairTarget?.blockResult?.blockPos ?: return + if (safeContext.blockState(pos).block !is ButtonBlock) { + StashMover.warn("Given position does not contain a button!") + return + } + pearlDispensePos = pos + StashMover.info("Set pearl button position!") + } + + context(safeContext: SafeContext) + fun setPearlThrow() { + pearlThrowPos = safeContext.player.blockPos + pearlRotation = safeContext.player.rotation + StashMover.info("Set pearl throw position and rotation!") + } + + context(safeContext: SafeContext) + fun setPearlBotButton() { + val pos = mc.crosshairTarget?.blockResult?.blockPos ?: return + if (safeContext.blockState(pos).block !is ButtonBlock) { + StashMover.warn("Given position does not contain a button!") + return + } + pearlBotButton = pos + StashMover.info("Set pearl bot button position!") + } + + fun startStop() { + chestPullSelMode = false + chestPutSelMode = false + task?.let { runningTask -> + runningTask.cancel() + task = null + return + } + StashMover.info("Starting ${if (role == Role.MoverBot) "MoverBot" else "PearlBot"}!") + task = role.createTask().run() + } + private fun consumeSelection(callback: (pos: BlockPos) -> Unit) { sel1?.let { sel1 -> sel2?.let { sel2 -> @@ -285,123 +346,17 @@ object StashMover : Module( } when (moverState) { - MoverState.OpeningPullContainer -> { - val target = pullContainers.minByOrNull { it distSq player.blockPos } - ?: run { - success("Pull containers exhausted!") - return@listen - } - - OpenContainerTask( - target, - StashMover - ).finally { - pullContainer = target - moverState = MoverState.TakingItems - }.execute(this@MoverBot) - } - - MoverState.TakingItems -> { - val screenHandler = player.currentScreenHandler - if (screenHandler === player.playerScreenHandler) { - moverState = MoverState.OpeningPullContainer - return@listen - } - val pullSlots = screenHandler.containerSlots.filter { !it.stack.isEmpty } - if (player.hotbarAndInventoryStacks.any { it.isEmpty } && pullSlots.isNotEmpty()) { - val request = inventoryRequest(settleForLess = true) { - pullSlots.forEach { slot -> - quickMove(slot.id) - } - }.submit() - if (!request.done) return@listen - } - player.closeScreen() - pullContainer?.let { container -> - if (screenHandler.containerStacks.all { it.isEmpty }) { - pullContainers.remove(container) - pulledContainers.add(container) - if (player.hotbarAndInventoryStacks.any { it.isEmpty }) { - moverState = MoverState.OpeningPullContainer - return@listen - } - } - } - moverState = MoverState.MessagingForPearl - } - - MoverState.MessagingForPearl -> { - tickTimer.reset() - connection.sendChatCommand("msg $pearlBotName ${Math.random() * Double.MAX_VALUE}") - moverState = MoverState.AwaitingTeleport - } - + MoverState.OpeningPullContainer -> handleOpeningPullContainer() + MoverState.TakingItems -> handleTakingItems() + MoverState.MessagingForPearl -> handleMessagingForPearl() MoverState.AwaitingTeleport -> { tickTimer.tick() if (tickTimer.hasSurpassed(pearlMsgTimeout)) moverState = MoverState.MessagingForPearl } - - MoverState.OpeningPutContainer -> { - val target = putContainers.minByOrNull { it distSq player.blockPos } - ?: run { - success("Put containers are full!") - return@listen - } - - OpenContainerTask( - target, - StashMover - ).finally { - putContainer = target - moverState = MoverState.PuttingItems - }.execute(this@MoverBot) - } - - MoverState.PuttingItems -> { - val screenHandler = player.currentScreenHandler - if (screenHandler === player.playerScreenHandler) { - moverState = MoverState.OpeningPutContainer - return@listen - } - val putSlots = screenHandler.playerSlots.filter { !it.stack.isEmpty } - if (screenHandler.containerStacks.any { it.isEmpty } && putSlots.isNotEmpty()) { - val request = inventoryRequest(settleForLess = true) { - putSlots.forEach { slot -> - quickMove(slot.id) - } - }.submit() - if (!request.done) return@listen - } - player.closeScreen() - putContainer?.let { container -> - if (screenHandler.containerStacks.all { !it.isEmpty }) { - putContainers.remove(container) - filledContainers.add(container) - if (player.hotbarAndInventoryStacks.any { !it.isEmpty }) { - moverState = MoverState.OpeningPutContainer - return@listen - } - } - } - moverState = MoverState.DispensingPearl - } - - MoverState.DispensingPearl -> { - val dispensePos = pearlDispensePos ?: run { failure("No pearl button set!"); return@listen } - if (player.blockPos != dispensePos) { - BaritoneManager.setGoalAndPath(GoalBlock(dispensePos)) - return@listen - } - if (BaritoneManager.isActive) return@listen - getButtonPressTask(dispensePos) - ?.finally { - tickTimer.reset() - moverState = MoverState.AwaitingPearl - } - ?.execute(this@MoverBot) - } - + MoverState.OpeningPutContainer -> handleOpeningPutContainer() + MoverState.PuttingItems -> handlePuttingItems() + MoverState.DispensingPearl -> handleDispensingPearl() MoverState.AwaitingPearl -> { tickTimer.tick() if (player.hotbarAndInventoryStacks.any { it.item === Items.ENDER_PEARL }) { @@ -411,49 +366,8 @@ object StashMover : Module( if (tickTimer.hasSurpassed(pearlButtonTimeout)) moverState = MoverState.DispensingPearl } - - MoverState.ThrowingPearl -> { - val throwPos = pearlThrowPos ?: run { failure("No pearl throw pos set!"); return@listen } - if (player.blockPos != throwPos) { - BaritoneManager.setGoalAndPath(GoalBlock(throwPos)) - return@listen - } - if (BaritoneManager.isActive) return@listen - val rotation = pearlRotation ?: run { failure("No pearl rotation set!"); return@listen } - val rotationRequest = rotationRequest { - rotation(rotation) - }.submit() - if (!rotationRequest.done) return@listen - while (player.mainHandStack.item != Items.ENDER_PEARL) { - val hotbarSlot = player.hotbarSlots.firstOrNull { it.stack.item === Items.ENDER_PEARL } - if (hotbarSlot != null) { - val hotbarRequest = HotbarRequest(hotbarSlot.index, StashMover).submit() - if (!hotbarRequest.done) return@listen - break - } else { - val inventorySlot = player.allSlots.firstOrNull { it.stack.item === Items.ENDER_PEARL } - if (inventorySlot == null) { - failure("No pearl in inventory!") - return@listen - } - val inventoryRequest = inventoryRequest { - swap(inventorySlot.id, 0) - }.submit() - if (!inventoryRequest.done) return@listen - } - } - RotationManager.withoutVanillaOverrides { - interaction.interactItem(player, Hand.MAIN_HAND) - } - moverState = MoverState.Killing - } - - MoverState.Killing -> { - tickTimer.reset() - connection.sendChatCommand("kill") - moverState = MoverState.AwaitingDeath - } - + MoverState.ThrowingPearl -> handleThrowingPearl() + MoverState.Killing -> handleKilling() else -> {} } } @@ -465,13 +379,7 @@ object StashMover : Module( if (tickTimer.hasSurpassed(killRespawnTimeout)) moverState = MoverState.Killing } - - MoverState.Respawning -> { - tickTimer.reset() - mc.networkHandler?.sendPacket(ClientStatusC2SPacket(ClientStatusC2SPacket.Mode.PERFORM_RESPAWN)) - moverState = MoverState.AwaitingRespawn - } - + MoverState.Respawning -> handleRespawning() MoverState.AwaitingRespawn -> { tickTimer.tick() if (tickTimer.hasSurpassed(killRespawnTimeout)) @@ -502,6 +410,165 @@ object StashMover : Module( } } + private fun SafeContext.handleOpeningPullContainer() { + val target = pullContainers.minByOrNull { it distSq player.blockPos } + ?: run { + success("Pull containers exhausted!") + return + } + + OpenContainerTask( + target, + StashMover + ).finally { + pullContainer = target + moverState = MoverState.TakingItems + }.execute(this@MoverBot) + } + + private fun SafeContext.handleTakingItems() { + val screenHandler = player.currentScreenHandler + if (screenHandler === player.playerScreenHandler) { + moverState = MoverState.OpeningPullContainer + return + } + val pullSlots = screenHandler.containerSlots.filter { !it.stack.isEmpty } + if (player.hotbarAndInventoryStacks.any { it.isEmpty } && pullSlots.isNotEmpty()) { + val request = inventoryRequest(settleForLess = true) { + pullSlots.forEach { slot -> + quickMove(slot.id) + } + }.submit() + if (!request.done) return + } + player.closeHandledScreen() + pullContainer?.let { container -> + if (screenHandler.containerStacks.all { it.isEmpty }) { + pullContainers.remove(container) + pulledContainers.add(container) + if (player.hotbarAndInventoryStacks.any { it.isEmpty }) { + moverState = MoverState.OpeningPullContainer + return + } + } + } + moverState = MoverState.MessagingForPearl + } + + private fun SafeContext.handleMessagingForPearl() { + tickTimer.reset() + connection.sendChatCommand("msg $pearlBotName ${Math.random() * Double.MAX_VALUE}") + moverState = MoverState.AwaitingTeleport + } + + private fun SafeContext.handleOpeningPutContainer() { + val target = putContainers.minByOrNull { it distSq player.blockPos } + ?: run { + success("Put containers are full!") + return + } + + OpenContainerTask( + target, + StashMover + ).finally { + putContainer = target + moverState = MoverState.PuttingItems + }.execute(this@MoverBot) + } + + private fun SafeContext.handlePuttingItems() { + val screenHandler = player.currentScreenHandler + if (screenHandler === player.playerScreenHandler) { + moverState = MoverState.OpeningPutContainer + return + } + val putSlots = screenHandler.playerSlots.filter { !it.stack.isEmpty } + if (screenHandler.containerStacks.any { it.isEmpty } && putSlots.isNotEmpty()) { + val request = inventoryRequest(settleForLess = true) { + putSlots.forEach { slot -> + quickMove(slot.id) + } + }.submit() + if (!request.done) return + } + player.closeHandledScreen() + putContainer?.let { container -> + if (screenHandler.containerStacks.all { !it.isEmpty }) { + putContainers.remove(container) + filledContainers.add(container) + if (player.hotbarAndInventoryStacks.any { !it.isEmpty }) { + moverState = MoverState.OpeningPutContainer + return + } + } + } + moverState = MoverState.DispensingPearl + } + + private fun SafeContext.handleDispensingPearl() { + val dispensePos = pearlDispensePos ?: run { failure("No pearl button set!"); return } + if (player.blockPos != dispensePos) { + BaritoneManager.setGoalAndPath(GoalBlock(dispensePos)) + return + } + if (BaritoneManager.isActive) return + getButtonPressTask(dispensePos) + ?.finally { + tickTimer.reset() + moverState = MoverState.AwaitingPearl + } + ?.execute(this@MoverBot) + } + + private fun SafeContext.handleThrowingPearl() { + val throwPos = pearlThrowPos ?: run { failure("No pearl throw pos set!"); return } + if (player.blockPos != throwPos) { + BaritoneManager.setGoalAndPath(GoalBlock(throwPos)) + return + } + if (BaritoneManager.isActive) return + val rotation = pearlRotation ?: run { failure("No pearl rotation set!"); return } + val rotationRequest = rotationRequest { + rotation(rotation) + }.submit() + if (!rotationRequest.done) return + while (player.mainHandStack.item != Items.ENDER_PEARL) { + val hotbarSlot = player.hotbarSlots.firstOrNull { it.stack.item === Items.ENDER_PEARL } + if (hotbarSlot != null) { + val hotbarRequest = HotbarRequest(hotbarSlot.index, StashMover, nowOrNothing = false).submit() + if (!hotbarRequest.done) return + break + } else { + val inventorySlot = player.allSlots.firstOrNull { it.stack.item === Items.ENDER_PEARL } + if (inventorySlot == null) { + failure("No pearl in inventory!") + return + } + val inventoryRequest = inventoryRequest { + swap(inventorySlot.id, 0) + }.submit() + if (!inventoryRequest.done) return + } + } + RotationManager.withoutVanillaOverrides { + interaction.interactItem(player, Hand.MAIN_HAND) + } + moverState = MoverState.Killing + } + + private fun SafeContext.handleKilling() { + tickTimer.reset() + connection.sendChatCommand("kill") + moverState = MoverState.AwaitingDeath + } + + private fun handleRespawning() { + tickTimer.reset() + mc.networkHandler?.sendPacket(ClientStatusC2SPacket(ClientStatusC2SPacket.Mode.PERFORM_RESPAWN)) + moverState = MoverState.AwaitingRespawn + } + private enum class MoverState { OpeningPullContainer, TakingItems, From 2fb6a7674c28402d08833d5e61a51037ce8b2979 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Fri, 10 Apr 2026 16:14:48 +0100 Subject: [PATCH 03/19] better logs --- .../lambda/module/modules/world/StashMover.kt | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt index 3d9adf9fb..b77274933 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -50,6 +50,7 @@ import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockEntity import com.lambda.util.BlockUtils.blockState import com.lambda.util.Communication.info +import com.lambda.util.Communication.logError import com.lambda.util.Communication.warn import com.lambda.util.NamedEnum import com.lambda.util.TickTimer @@ -294,7 +295,12 @@ object StashMover : Module( return } StashMover.info("Starting ${if (role == Role.MoverBot) "MoverBot" else "PearlBot"}!") - task = role.createTask().run() + task = role + .createTask() + .finally { message -> + StashMover.info("Finished! $message") + } + .run() } private fun consumeSelection(callback: (pos: BlockPos) -> Unit) { @@ -507,7 +513,7 @@ object StashMover : Module( } private fun SafeContext.handleDispensingPearl() { - val dispensePos = pearlDispensePos ?: run { failure("No pearl button set!"); return } + val dispensePos = pearlDispensePos ?: run { failWithLog("No pearl button set!"); return } if (player.blockPos != dispensePos) { BaritoneManager.setGoalAndPath(GoalBlock(dispensePos)) return @@ -522,13 +528,13 @@ object StashMover : Module( } private fun SafeContext.handleThrowingPearl() { - val throwPos = pearlThrowPos ?: run { failure("No pearl throw pos set!"); return } + val throwPos = pearlThrowPos ?: run { failWithLog("No pearl throw pos set!"); return } if (player.blockPos != throwPos) { BaritoneManager.setGoalAndPath(GoalBlock(throwPos)) return } if (BaritoneManager.isActive) return - val rotation = pearlRotation ?: run { failure("No pearl rotation set!"); return } + val rotation = pearlRotation ?: run { failWithLog("No pearl rotation set!"); return } val rotationRequest = rotationRequest { rotation(rotation) }.submit() @@ -542,7 +548,7 @@ object StashMover : Module( } else { val inventorySlot = player.allSlots.firstOrNull { it.stack.item === Items.ENDER_PEARL } if (inventorySlot == null) { - failure("No pearl in inventory!") + failWithLog("No pearl in inventory!") return } val inventoryRequest = inventoryRequest { @@ -586,7 +592,7 @@ object StashMover : Module( } } - private class PearlBot : Task() { + private class PearlBot : Task() { override val name get() = "Pearling $moverBotName for stash moving, current state: $pearlState" var pearlState = PearlState.Waiting @@ -595,7 +601,7 @@ object StashMover : Module( listen { when (pearlState) { PearlState.Pressing -> { - val buttonPos = pearlBotButton ?: run { failure("No pearl bot button set!"); return@listen } + val buttonPos = pearlBotButton ?: run { failWithLog("No pearl bot button set!"); return@listen } getButtonPressTask(buttonPos) ?.finally { pearlState = PearlState.Waiting @@ -619,7 +625,7 @@ object StashMover : Module( } context(safeContext: SafeContext) - private fun Task.getButtonPressTask(pos: BlockPos): Task<*>? = + private fun Task<*>.getButtonPressTask(pos: BlockPos): Task<*>? = with (safeContext) { val buttonState = blockState(pos) if (buttonState.block !is ButtonBlock) { @@ -632,4 +638,9 @@ object StashMover : Module( } } else null } + + private fun Task<*>.failWithLog(message: String) { + failure(message) + StashMover.logError(message) + } } \ No newline at end of file From ea0fed4c8585bfea94345228787bcd33f12c4a09 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Fri, 10 Apr 2026 17:07:39 +0100 Subject: [PATCH 04/19] disconnectOnFinish and disconnectOnFail settings --- .../lambda/module/modules/world/StashMover.kt | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt index b77274933..d7347373b 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -63,6 +63,10 @@ import com.lambda.util.math.setAlpha import com.lambda.util.player.SlotUtils.allSlots import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks import com.lambda.util.player.SlotUtils.hotbarSlots +import com.lambda.util.text.bold +import com.lambda.util.text.buildText +import com.lambda.util.text.color +import com.lambda.util.text.literal import com.lambda.util.world.raycast.RayCastUtils.blockResult import net.minecraft.block.ButtonBlock import net.minecraft.block.entity.LootableContainerBlockEntity @@ -114,6 +118,8 @@ object StashMover : Module( private val pearlButtonTimeout by setting("Pearl Button Timeout", 100, 0..1500, 1, "Ticks before pressing the pearl dispenser button again", "ticks") { role == Role.MoverBot }.group(Group.General) private val killRespawnTimeout by setting("Kill/Respawn Timeout", 100, 0..1500, 1, "Ticks before sending the kill command or attempting to respawn again", "ticks") { role == Role.MoverBot }.group(Group.General) private val actionDelay by setting("Action Delay", 3, 0..20, 1, "The delay after performing one action, before the next") { role == Role.MoverBot }.group(Group.General) + private val disconnectOnFinish by setting("Disconnect On Finish", false, "Disconnects the mover bot when it's finished") { role == Role.MoverBot } + private val disconnectOnFail by setting("Disconnect On Fail", false, "Disconnects the mover bot if it fails") { role == Role.MoverBot } private val startStop by setting("Start/Stop", Bind.EMPTY, "Starts and stops the selected role").group(Group.General) .onPress { event -> event.cancel() @@ -297,8 +303,27 @@ object StashMover : Module( StashMover.info("Starting ${if (role == Role.MoverBot) "MoverBot" else "PearlBot"}!") task = role .createTask() + .onFailOrNull { + if (disconnectOnFail) connection.connection.disconnect( + buildText { + bold { + literal("StashMover") + color(Color.RED) { literal(" Failed") } + } + } + ) + null + } .finally { message -> StashMover.info("Finished! $message") + if (disconnectOnFinish) connection.connection.disconnect( + buildText { + bold { + literal("StashMover") + color(Color.GREEN) { literal(" Finished!") } + } + } + ) } .run() } @@ -343,6 +368,9 @@ object StashMover : Module( private var putContainer: BlockPos? = null + private var finished = false + private var finishedMessage = "" + init { listen { if (delayingNextAction) { @@ -406,6 +434,10 @@ object StashMover : Module( if (moverState != MoverState.AwaitingTeleport) return@listen val packet = event.packet if (packet !is PlayerPositionLookS2CPacket) return@listen + if (finished && player.hotbarAndInventoryStacks.all { it.isEmpty }) { + success(finishedMessage) + return@listen + } moverState = MoverState.OpeningPutContainer } @@ -419,7 +451,9 @@ object StashMover : Module( private fun SafeContext.handleOpeningPullContainer() { val target = pullContainers.minByOrNull { it distSq player.blockPos } ?: run { - success("Pull containers exhausted!") + finished = true + finishedMessage = "Pull containers exhausted!" + moverState = MoverState.MessagingForPearl return } @@ -509,6 +543,10 @@ object StashMover : Module( } } } + if (finished) { + success(finishedMessage) + return + } moverState = MoverState.DispensingPearl } From d42a097f1009c4d2d86793a88b935e51a50d222c Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Fri, 10 Apr 2026 17:10:25 +0100 Subject: [PATCH 05/19] disconnect settings in the general group --- src/main/kotlin/com/lambda/module/modules/world/StashMover.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt index d7347373b..a23017bed 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -118,8 +118,8 @@ object StashMover : Module( private val pearlButtonTimeout by setting("Pearl Button Timeout", 100, 0..1500, 1, "Ticks before pressing the pearl dispenser button again", "ticks") { role == Role.MoverBot }.group(Group.General) private val killRespawnTimeout by setting("Kill/Respawn Timeout", 100, 0..1500, 1, "Ticks before sending the kill command or attempting to respawn again", "ticks") { role == Role.MoverBot }.group(Group.General) private val actionDelay by setting("Action Delay", 3, 0..20, 1, "The delay after performing one action, before the next") { role == Role.MoverBot }.group(Group.General) - private val disconnectOnFinish by setting("Disconnect On Finish", false, "Disconnects the mover bot when it's finished") { role == Role.MoverBot } - private val disconnectOnFail by setting("Disconnect On Fail", false, "Disconnects the mover bot if it fails") { role == Role.MoverBot } + private val disconnectOnFinish by setting("Disconnect On Finish", false, "Disconnects the mover bot when it's finished") { role == Role.MoverBot }.group(Group.General) + private val disconnectOnFail by setting("Disconnect On Fail", false, "Disconnects the mover bot if it fails") { role == Role.MoverBot }.group(Group.General) private val startStop by setting("Start/Stop", Bind.EMPTY, "Starts and stops the selected role").group(Group.General) .onPress { event -> event.cancel() From 742288d6b1d73ba055ca59402a643af67af0ba24 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Fri, 10 Apr 2026 20:51:05 +0100 Subject: [PATCH 06/19] fixed packet limits --- .../com/lambda/config/groups/BuildSettings.kt | 4 +- .../managers/PacketLimitHandler.kt | 51 ++++++++++--------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt b/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt index ab1187b11..91e067dd7 100644 --- a/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt @@ -48,8 +48,8 @@ class BuildSettings( override val actionTimeout by c.setting("${prefix}Action Timeout", 10, 1..30, 1, "Timeout for block breaks in ticks", unit = " ticks", visibility = visibility).group(*baseGroup, Group.General).index() override val maxBuildDependencies by c.setting("${prefix}Max Sim Dependencies", 3, 0..10, 1, "Maximum dependency build results", visibility = visibility).group(*baseGroup, Group.General).index() - override val limitTimeframe by c.setting("${prefix}Limit Timeframe", 6, 1..30, 1, "The timeframe in which the limit is bound to", "ticks", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() - override val actionLimit by c.setting("${prefix}Action Limit", 50, 1..100, 1, "The maximum allowed action packets to be sent to the server per given timeframe", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() + override val limitTimeframe by c.setting("${prefix}Limit Timeframe", 310, 50..1500, 1, "The timeframe in which the limit is bound to", "ms", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() + override val actionLimit by c.setting("${prefix}Action Limit", 59, 1..100, 1, "The maximum allowed action packets to be sent to the server per given timeframe", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() override val interactionPacketLimit by c.setting("Interaction Limit", 9, 1..20, 1, "The maximum allowed interaction packets to be sent to the server per given timeframe", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() override var blockReach by c.setting("${prefix}Interact Reach", 4.5, 1.0..7.0, 0.01, "Maximum block interaction distance", visibility = visibility).group(*baseGroup, Group.Reach).index() diff --git a/src/main/kotlin/com/lambda/interaction/managers/PacketLimitHandler.kt b/src/main/kotlin/com/lambda/interaction/managers/PacketLimitHandler.kt index 9190a39da..32db944e8 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/PacketLimitHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/PacketLimitHandler.kt @@ -21,41 +21,46 @@ import com.lambda.config.groups.BuildConfig import com.lambda.context.Automated import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.util.TickTimer object PacketLimitHandler { - private val packetLimitMap = PacketType.entries.associateWith { LimitHandler(0, 0, 0) } + private val packetLimitMap = PacketType.entries.associateWith { LimitHandler() } init { listen(priority = { Int.MAX_VALUE }) { - packetLimitMap.values.forEach { limitHandler -> - with(limitHandler) { - tickTimer.tick() - if (tickTimer.hasSurpassed(timeframe)) { - packetsThisTimeframe = 0 - tickTimer.reset() - } - } - } + val currentTime = System.currentTimeMillis() + packetLimitMap.values.forEach { it.removeStale(currentTime) } } } context(automated: Automated) - fun canSendPackets(packetCount: Int, packetType: PacketType) = - packetLimitMap[packetType]?.let { - it.maxPacketsThisTimeframe = packetType.maxPacketsPerTimeframe(automated.buildConfig) - it.timeframe = automated.buildConfig.limitTimeframe - it.packetsThisTimeframe + packetCount <= it.maxPacketsThisTimeframe - } ?: false + fun canSendPackets(packetCount: Int, packetType: PacketType): Boolean { + val handler = packetLimitMap[packetType] ?: return false + handler.maxPacketsThisTimeframe = packetType.maxPacketsPerTimeframe(automated.buildConfig) + handler.timeframeMillis = automated.buildConfig.limitTimeframe + handler.removeStale(System.currentTimeMillis()) + return handler.packetTimestamps.size + packetCount <= handler.maxPacketsThisTimeframe + } - fun sentPackets(packetCount: Int, packetType: PacketType) = - packetLimitMap[packetType]?.let { limitHandler -> - if (limitHandler.packetsThisTimeframe == 0) limitHandler.tickTimer.reset() - limitHandler.packetsThisTimeframe += packetCount + fun sentPackets(packetCount: Int, packetType: PacketType) { + packetLimitMap[packetType]?.let { handler -> + val currentTime = System.currentTimeMillis() + repeat((0 until packetCount).count()) { + handler.packetTimestamps.addLast(currentTime) + } } + } - private data class LimitHandler(var maxPacketsThisTimeframe: Int, var packetsThisTimeframe: Int, var timeframe: Int) { - val tickTimer = TickTimer() + private class LimitHandler { + var maxPacketsThisTimeframe = 0 + var timeframeMillis = 0 + val packetTimestamps = ArrayDeque() + + fun removeStale(currentTime: Long) { + if (timeframeMillis <= 0) return + while (packetTimestamps.isNotEmpty() && packetTimestamps.first() <= currentTime - timeframeMillis) { + packetTimestamps.removeFirst() + } + } } } From 4b36128df105d4c696b6d447d7d0d6bf0a43d371 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Fri, 10 Apr 2026 23:30:02 +0100 Subject: [PATCH 07/19] automation config migration --- .../migrations/AutomationConfigMigration.kt | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/kotlin/com/lambda/config/migration/migrations/AutomationConfigMigration.kt diff --git a/src/main/kotlin/com/lambda/config/migration/migrations/AutomationConfigMigration.kt b/src/main/kotlin/com/lambda/config/migration/migrations/AutomationConfigMigration.kt new file mode 100644 index 000000000..cef85a53b --- /dev/null +++ b/src/main/kotlin/com/lambda/config/migration/migrations/AutomationConfigMigration.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2026 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.config.migration.migrations + +import com.lambda.Lambda.LOG +import com.lambda.config.migration.StepConfigMigration + +object AutomationConfigMigration : StepConfigMigration() { + override val configName = "automation" + override val latestVersion = 2 + + init { + step(1, 2) { + var updateCount = 0 + entrySet().forEach { configPair -> + getAsJsonObject(configPair.key)?.let { config -> + config.get("Limit Timeframe")?.let { limitTimeframe -> + if (limitTimeframe.asInt < 50) { + config.addProperty("Limit Timeframe", 310) + updateCount++ + } + } + } + } + + LOG.info("Migrated Automation config schema v1 -> v2: $updateCount settings updated") + } + } +} \ No newline at end of file From 47b0899463d8a769be3c946a717edd225f74a625 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Sat, 11 Apr 2026 14:54:02 +0100 Subject: [PATCH 08/19] use kotlin Durations --- .../com/lambda/config/groups/BuildConfig.kt | 2 +- .../com/lambda/config/groups/BuildSettings.kt | 2 +- .../managers/PacketLimitHandler.kt | 24 +++++++++++-------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt b/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt index 81c36d0e3..93896f003 100644 --- a/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt +++ b/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt @@ -37,7 +37,7 @@ interface BuildConfig : ISettingGroup { val limitTimeframe: Int val actionLimit: Int - val interactionPacketLimit: Int + val interactionLimit: Int val blockReach: Double val entityReach: Double diff --git a/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt b/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt index 91e067dd7..48593da33 100644 --- a/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt @@ -50,7 +50,7 @@ class BuildSettings( override val limitTimeframe by c.setting("${prefix}Limit Timeframe", 310, 50..1500, 1, "The timeframe in which the limit is bound to", "ms", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() override val actionLimit by c.setting("${prefix}Action Limit", 59, 1..100, 1, "The maximum allowed action packets to be sent to the server per given timeframe", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() - override val interactionPacketLimit by c.setting("Interaction Limit", 9, 1..20, 1, "The maximum allowed interaction packets to be sent to the server per given timeframe", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() + override val interactionLimit by c.setting("Interaction Limit", 9, 1..20, 1, "The maximum allowed interaction packets to be sent to the server per given timeframe", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() override var blockReach by c.setting("${prefix}Interact Reach", 4.5, 1.0..7.0, 0.01, "Maximum block interaction distance", visibility = visibility).group(*baseGroup, Group.Reach).index() override var entityReach by c.setting("${prefix}Attack Reach", 3.0, 1.0..7.0, 0.01, "Maximum entity interaction distance", visibility = visibility).group(*baseGroup, Group.Reach).index() diff --git a/src/main/kotlin/com/lambda/interaction/managers/PacketLimitHandler.kt b/src/main/kotlin/com/lambda/interaction/managers/PacketLimitHandler.kt index 32db944e8..4d32cd3a7 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/PacketLimitHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/PacketLimitHandler.kt @@ -21,13 +21,17 @@ import com.lambda.config.groups.BuildConfig import com.lambda.context.Automated import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.ComparableTimeMark +import kotlin.time.TimeSource object PacketLimitHandler { private val packetLimitMap = PacketType.entries.associateWith { LimitHandler() } init { listen(priority = { Int.MAX_VALUE }) { - val currentTime = System.currentTimeMillis() + val currentTime = TimeSource.Monotonic.markNow() packetLimitMap.values.forEach { it.removeStale(currentTime) } } } @@ -36,14 +40,14 @@ object PacketLimitHandler { fun canSendPackets(packetCount: Int, packetType: PacketType): Boolean { val handler = packetLimitMap[packetType] ?: return false handler.maxPacketsThisTimeframe = packetType.maxPacketsPerTimeframe(automated.buildConfig) - handler.timeframeMillis = automated.buildConfig.limitTimeframe - handler.removeStale(System.currentTimeMillis()) + handler.timeframe = automated.buildConfig.limitTimeframe.milliseconds + handler.removeStale(TimeSource.Monotonic.markNow()) return handler.packetTimestamps.size + packetCount <= handler.maxPacketsThisTimeframe } fun sentPackets(packetCount: Int, packetType: PacketType) { packetLimitMap[packetType]?.let { handler -> - val currentTime = System.currentTimeMillis() + val currentTime = TimeSource.Monotonic.markNow() repeat((0 until packetCount).count()) { handler.packetTimestamps.addLast(currentTime) } @@ -52,12 +56,12 @@ object PacketLimitHandler { private class LimitHandler { var maxPacketsThisTimeframe = 0 - var timeframeMillis = 0 - val packetTimestamps = ArrayDeque() + var timeframe: Duration = Duration.ZERO + val packetTimestamps = ArrayDeque() - fun removeStale(currentTime: Long) { - if (timeframeMillis <= 0) return - while (packetTimestamps.isNotEmpty() && packetTimestamps.first() <= currentTime - timeframeMillis) { + fun removeStale(currentTime: ComparableTimeMark) { + if (timeframe <= Duration.ZERO) return + while (packetTimestamps.isNotEmpty() && packetTimestamps.first() <= currentTime - timeframe) { packetTimestamps.removeFirst() } } @@ -66,5 +70,5 @@ object PacketLimitHandler { enum class PacketType(val maxPacketsPerTimeframe: BuildConfig.() -> Int) { PlayerAction({ actionLimit }), - Interaction({ interactionPacketLimit }) + Interaction({ interactionLimit }) } \ No newline at end of file From a95247e58e4cd64dda73c3d6c5f6247961184698 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Sun, 12 Apr 2026 14:43:18 +0100 Subject: [PATCH 09/19] use ender chest option --- .../lambda/module/modules/world/StashMover.kt | 255 +++++++++++------- 1 file changed, 165 insertions(+), 90 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt index a23017bed..08b968a5d 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -40,7 +40,9 @@ import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inve import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.Rotation import com.lambda.interaction.managers.rotating.RotationManager +import com.lambda.interaction.material.container.containers.EnderChestContainer import com.lambda.module.Module +import com.lambda.module.modules.combat.KillAura.target import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask.run import com.lambda.task.Task @@ -56,11 +58,11 @@ import com.lambda.util.NamedEnum import com.lambda.util.TickTimer import com.lambda.util.extension.containerSlots import com.lambda.util.extension.containerStacks -import com.lambda.util.extension.playerSlots import com.lambda.util.extension.rotation import com.lambda.util.math.distSq import com.lambda.util.math.setAlpha import com.lambda.util.player.SlotUtils.allSlots +import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks import com.lambda.util.player.SlotUtils.hotbarSlots import com.lambda.util.text.bold @@ -68,19 +70,24 @@ import com.lambda.util.text.buildText import com.lambda.util.text.color import com.lambda.util.text.literal import com.lambda.util.world.raycast.RayCastUtils.blockResult +import net.minecraft.block.Blocks import net.minecraft.block.ButtonBlock import net.minecraft.block.entity.LootableContainerBlockEntity import net.minecraft.client.gui.screen.DeathScreen +import net.minecraft.item.ItemStack import net.minecraft.item.Items import net.minecraft.network.packet.c2s.play.ClientStatusC2SPacket import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket import net.minecraft.network.packet.s2c.play.PlayerRespawnS2CPacket +import net.minecraft.screen.ScreenHandler +import net.minecraft.screen.slot.Slot import net.minecraft.state.property.Properties import net.minecraft.util.Hand import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Box import org.lwjgl.glfw.GLFW import java.awt.Color +import kotlin.math.min import kotlin.run import kotlin.to @@ -118,6 +125,13 @@ object StashMover : Module( private val pearlButtonTimeout by setting("Pearl Button Timeout", 100, 0..1500, 1, "Ticks before pressing the pearl dispenser button again", "ticks") { role == Role.MoverBot }.group(Group.General) private val killRespawnTimeout by setting("Kill/Respawn Timeout", 100, 0..1500, 1, "Ticks before sending the kill command or attempting to respawn again", "ticks") { role == Role.MoverBot }.group(Group.General) private val actionDelay by setting("Action Delay", 3, 0..20, 1, "The delay after performing one action, before the next") { role == Role.MoverBot }.group(Group.General) + private val useEnderChest by setting("Use Ender Chest", false, "Uses the ender chest to move more items at once") { role == Role.MoverBot }.group(Group.General) + .onValueChange { _, to -> + if (!to) { + pullEnderChests.clear() + putEnderChests.clear() + } + } private val disconnectOnFinish by setting("Disconnect On Finish", false, "Disconnects the mover bot when it's finished") { role == Role.MoverBot }.group(Group.General) private val disconnectOnFail by setting("Disconnect On Fail", false, "Disconnects the mover bot if it fails") { role == Role.MoverBot }.group(Group.General) private val startStop by setting("Start/Stop", Bind.EMPTY, "Starts and stops the selected role").group(Group.General) @@ -156,8 +170,10 @@ object StashMover : Module( private var sel2: BlockPos? = null private val pullContainers = hashSetOf() + private val pullEnderChests = hashSetOf() private val pulledContainers = hashSetOf() private val putContainers = hashSetOf() + private val putEnderChests = hashSetOf() private val filledContainers = hashSetOf() private var pearlDispensePos: BlockPos? = null @@ -234,15 +250,25 @@ object StashMover : Module( fun indexSelectedContainers() { var addCount = 0 consumeSelection { pos -> - if (safeContext.blockEntity(pos) !is LootableContainerBlockEntity) return@consumeSelection - addCount++ pulledContainers.remove(pos) filledContainers.remove(pos) + if (useEnderChest && safeContext.blockState(pos).block === Blocks.ENDER_CHEST) { + addCount++ + if (chestPullSelMode) { + putEnderChests.remove(pos) + pullEnderChests.add(pos) + } else if (chestPutSelMode) { + pullEnderChests.remove(pos) + putEnderChests.add(pos) + } + return@consumeSelection + } + if (safeContext.blockEntity(pos) !is LootableContainerBlockEntity) return@consumeSelection + addCount++ if (chestPullSelMode) { putContainers.remove(pos) pullContainers.add(pos) - } - else if (chestPutSelMode) { + } else if (chestPutSelMode) { pullContainers.remove(pos) putContainers.add(pos) } @@ -379,27 +405,57 @@ object StashMover : Module( delayingNextAction = false } + val screenHandler = player.currentScreenHandler + when (moverState) { - MoverState.OpeningPullContainer -> handleOpeningPullContainer() - MoverState.TakingItems -> handleTakingItems() + MoverState.OpeningPullContainer -> + openClosestContainer( + pullContainers, + { + finished = true + finishedMessage = "Pull containers exhausted!" + moverState = MoverState.MessagingForPearl + } + ) { pos -> + pullContainer = pos + moverState = MoverState.TakingItems + } + MoverState.TakingItems -> handleTakingItems(screenHandler) + MoverState.OpeningPullEnderChest -> + openClosestContainer( + pullEnderChests, + { failWithLog("No pull ender chests indexed!") } + ) { moverState = MoverState.PuttingInEnderChest } + MoverState.PuttingInEnderChest -> handlePuttingInEnderChest(screenHandler) MoverState.MessagingForPearl -> handleMessagingForPearl() - MoverState.AwaitingTeleport -> { - tickTimer.tick() - if (tickTimer.hasSurpassed(pearlMsgTimeout)) - moverState = MoverState.MessagingForPearl - } - MoverState.OpeningPutContainer -> handleOpeningPutContainer() - MoverState.PuttingItems -> handlePuttingItems() + MoverState.AwaitingTeleport -> + checkTimerProgress( + MoverState.MessagingForPearl, + pearlMsgTimeout + ) + MoverState.OpeningPutContainer -> + openClosestContainer( + putContainers, + { success("Put containers are full!") } + ) { pos -> + putContainer = pos + moverState = MoverState.PuttingItems + } + MoverState.PuttingItems -> handlePuttingItems(screenHandler) + MoverState.OpeningPutEnderChest -> + openClosestContainer( + putEnderChests, + { failWithLog("No put ender chests indexed!") } + ) { moverState = MoverState.PullingFromEnderChest } + MoverState.PullingFromEnderChest -> handlePullingFromEnderChest(screenHandler) MoverState.DispensingPearl -> handleDispensingPearl() - MoverState.AwaitingPearl -> { - tickTimer.tick() - if (player.hotbarAndInventoryStacks.any { it.item === Items.ENDER_PEARL }) { - moverState = MoverState.ThrowingPearl - return@listen + MoverState.AwaitingPearl -> + checkTimerProgress(MoverState.DispensingPearl, pearlButtonTimeout) { + if (player.hotbarAndInventoryStacks.any { it.item === Items.ENDER_PEARL }) { + moverState = MoverState.ThrowingPearl + true + } else false } - if (tickTimer.hasSurpassed(pearlButtonTimeout)) - moverState = MoverState.DispensingPearl - } MoverState.ThrowingPearl -> handleThrowingPearl() MoverState.Killing -> handleKilling() else -> {} @@ -408,18 +464,9 @@ object StashMover : Module( listenUnsafe { when (moverState) { - MoverState.AwaitingDeath -> { - tickTimer.tick() - if (tickTimer.hasSurpassed(killRespawnTimeout)) - moverState = MoverState.Killing - } + MoverState.AwaitingDeath -> checkTimerProgress(MoverState.Killing, killRespawnTimeout) MoverState.Respawning -> handleRespawning() - MoverState.AwaitingRespawn -> { - tickTimer.tick() - if (tickTimer.hasSurpassed(killRespawnTimeout)) - moverState = MoverState.Respawning - } - + MoverState.AwaitingRespawn -> checkTimerProgress(MoverState.Respawning, killRespawnTimeout) else -> {} } } @@ -448,40 +495,12 @@ object StashMover : Module( } } - private fun SafeContext.handleOpeningPullContainer() { - val target = pullContainers.minByOrNull { it distSq player.blockPos } - ?: run { - finished = true - finishedMessage = "Pull containers exhausted!" - moverState = MoverState.MessagingForPearl - return - } - - OpenContainerTask( - target, - StashMover - ).finally { - pullContainer = target - moverState = MoverState.TakingItems - }.execute(this@MoverBot) - } - - private fun SafeContext.handleTakingItems() { - val screenHandler = player.currentScreenHandler + private fun SafeContext.handleTakingItems(screenHandler: ScreenHandler) { if (screenHandler === player.playerScreenHandler) { moverState = MoverState.OpeningPullContainer return } - val pullSlots = screenHandler.containerSlots.filter { !it.stack.isEmpty } - if (player.hotbarAndInventoryStacks.any { it.isEmpty } && pullSlots.isNotEmpty()) { - val request = inventoryRequest(settleForLess = true) { - pullSlots.forEach { slot -> - quickMove(slot.id) - } - }.submit() - if (!request.done) return - } - player.closeHandledScreen() + if (!moveFromContainerToContainer(screenHandler.containerSlots, player.hotbarAndInventoryStacks)) return pullContainer?.let { container -> if (screenHandler.containerStacks.all { it.isEmpty }) { pullContainers.remove(container) @@ -492,46 +511,35 @@ object StashMover : Module( } } } + if (useEnderChest && EnderChestContainer.stacks.any { it.isEmpty }) { + moverState = MoverState.OpeningPullEnderChest + return + } moverState = MoverState.MessagingForPearl } + private fun SafeContext.handlePuttingInEnderChest(screenHandler: ScreenHandler) { + if (screenHandler === player.playerScreenHandler) { + moverState = MoverState.OpeningPullEnderChest + return + } + if (moveFromContainerToContainer(player.hotbarAndInventorySlots, screenHandler.containerStacks)) { + moverState = MoverState.OpeningPullContainer + } + } + private fun SafeContext.handleMessagingForPearl() { tickTimer.reset() connection.sendChatCommand("msg $pearlBotName ${Math.random() * Double.MAX_VALUE}") moverState = MoverState.AwaitingTeleport } - private fun SafeContext.handleOpeningPutContainer() { - val target = putContainers.minByOrNull { it distSq player.blockPos } - ?: run { - success("Put containers are full!") - return - } - - OpenContainerTask( - target, - StashMover - ).finally { - putContainer = target - moverState = MoverState.PuttingItems - }.execute(this@MoverBot) - } - - private fun SafeContext.handlePuttingItems() { - val screenHandler = player.currentScreenHandler + private fun SafeContext.handlePuttingItems(screenHandler: ScreenHandler) { if (screenHandler === player.playerScreenHandler) { moverState = MoverState.OpeningPutContainer return } - val putSlots = screenHandler.playerSlots.filter { !it.stack.isEmpty } - if (screenHandler.containerStacks.any { it.isEmpty } && putSlots.isNotEmpty()) { - val request = inventoryRequest(settleForLess = true) { - putSlots.forEach { slot -> - quickMove(slot.id) - } - }.submit() - if (!request.done) return - } + if (!moveFromContainerToContainer(player.hotbarAndInventorySlots, screenHandler.containerStacks)) return player.closeHandledScreen() putContainer?.let { container -> if (screenHandler.containerStacks.all { !it.isEmpty }) { @@ -543,6 +551,10 @@ object StashMover : Module( } } } + if (useEnderChest && EnderChestContainer.stacks.any { !it.isEmpty }) { + moverState = MoverState.OpeningPutEnderChest + return + } if (finished) { success(finishedMessage) return @@ -550,6 +562,16 @@ object StashMover : Module( moverState = MoverState.DispensingPearl } + private fun SafeContext.handlePullingFromEnderChest(screenHandler: ScreenHandler) { + if (screenHandler === player.playerScreenHandler) { + moverState = MoverState.OpeningPutEnderChest + return + } + if (moveFromContainerToContainer(screenHandler.containerSlots, player.hotbarAndInventoryStacks)) { + moverState = MoverState.OpeningPutContainer + } + } + private fun SafeContext.handleDispensingPearl() { val dispensePos = pearlDispensePos ?: run { failWithLog("No pearl button set!"); return } if (player.blockPos != dispensePos) { @@ -613,13 +635,66 @@ object StashMover : Module( moverState = MoverState.AwaitingRespawn } + private fun SafeContext.openClosestContainer( + positions: Collection, + onNoneAvailable: () -> Unit, + finally: SafeContext.(pos: BlockPos) -> Unit + ) { + val pos = positions.minByOrNull { it distSq player.blockPos } + ?: run { + onNoneAvailable() + return + } + + OpenContainerTask( + pos, + StashMover + ).finally { + finally(pos) + }.execute(this@MoverBot) + } + + private fun SafeContext.moveFromContainerToContainer( + from: Collection, + to: Collection + ): Boolean { + val filteredFrom = from.filter { !it.stack.isEmpty } + val filteredTo = to.filter { it.isEmpty } + if (filteredTo.isEmpty() || filteredFrom.isEmpty()) return true + val moveSlots = filteredFrom.subList(0, min(filteredTo.size, filteredFrom.size)) + if (moveSlots.isNotEmpty()) { + val request = inventoryRequest(settleForLess = true) { + moveSlots.forEach { slot -> + quickMove(slot.id) + } + }.submit() + if (!request.done) return false + } + player.closeHandledScreen() + return true + } + + private fun checkTimerProgress( + fallbackState: MoverState, + timeout: Int, + progressionCheck: (() -> Boolean)? = null + ) { + tickTimer.tick() + if (progressionCheck?.invoke() == true) return + if (tickTimer.hasSurpassed(timeout)) moverState = fallbackState + } + private enum class MoverState { OpeningPullContainer, TakingItems, + OpeningPullEnderChest, + PuttingInEnderChest, MessagingForPearl, AwaitingTeleport, OpeningPutContainer, PuttingItems, + OpeningPutEnderChest, + PullingFromEnderChest, DispensingPearl, AwaitingPearl, ThrowingPearl, From aa72eaf65ebb4cea303e1c388c937924e59fd345 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Sun, 12 Apr 2026 15:36:41 +0100 Subject: [PATCH 10/19] break empty pull containers option --- .../lambda/module/modules/world/StashMover.kt | 128 +++++++++++------- .../lambda/task/tasks/OpenContainerTask.kt | 1 + 2 files changed, 81 insertions(+), 48 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt index 08b968a5d..72a77aedf 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -31,7 +31,7 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe -import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer +import com.lambda.graphics.mc.renderer.TickedRenderer.Companion.tickedRenderer import com.lambda.interaction.BaritoneManager import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.managers.hotbar.HotbarRequest @@ -42,7 +42,6 @@ import com.lambda.interaction.managers.rotating.Rotation import com.lambda.interaction.managers.rotating.RotationManager import com.lambda.interaction.material.container.containers.EnderChestContainer import com.lambda.module.Module -import com.lambda.module.modules.combat.KillAura.target import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask.run import com.lambda.task.Task @@ -51,6 +50,7 @@ import com.lambda.task.tasks.OpenContainerTask import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockEntity import com.lambda.util.BlockUtils.blockState +import com.lambda.util.BlockUtils.emptyState import com.lambda.util.Communication.info import com.lambda.util.Communication.logError import com.lambda.util.Communication.warn @@ -107,14 +107,7 @@ object StashMover : Module( } private val role: Role by setting("Role", Role.MoverBot).group(Group.General) - .onValueChange { _, to -> - if (to == Role.PearlBot) { - chestPullSelMode = false - chestPutSelMode = false - sel1 = null - sel2 = null - } - } + .onValueChange { _, to -> if (to == Role.PearlBot) clearModule() } private val pearlBotName by setting("PearlBot Name", "Steve") { role == Role.MoverBot }.group(Group.General) private val moverBotName by setting("MoverBot Name", "Steve") { role == Role.PearlBot }.group(Group.General) private var chestPullSelMode: Boolean by setting("Chest Pull Sel Mode", false, "Enables the mode to select the stash containers you want to move items from") { role == Role.MoverBot }.group(Group.General) @@ -125,13 +118,14 @@ object StashMover : Module( private val pearlButtonTimeout by setting("Pearl Button Timeout", 100, 0..1500, 1, "Ticks before pressing the pearl dispenser button again", "ticks") { role == Role.MoverBot }.group(Group.General) private val killRespawnTimeout by setting("Kill/Respawn Timeout", 100, 0..1500, 1, "Ticks before sending the kill command or attempting to respawn again", "ticks") { role == Role.MoverBot }.group(Group.General) private val actionDelay by setting("Action Delay", 3, 0..20, 1, "The delay after performing one action, before the next") { role == Role.MoverBot }.group(Group.General) - private val useEnderChest by setting("Use Ender Chest", false, "Uses the ender chest to move more items at once") { role == Role.MoverBot }.group(Group.General) + private val useEnderChest by setting("Use Ender Chest", false, "Uses the ender chest to move more items at once. (Ender chests are included in the pull/put selections)") { role == Role.MoverBot }.group(Group.General) .onValueChange { _, to -> if (!to) { - pullEnderChests.clear() putEnderChests.clear() + pullEnderChests.clear() } } + private val breakEmptyPullContainers by setting("Break Empty Pull Containers", false, "Breaks empty pull containers after taking their items") { role == Role.MoverBot }.group(Group.General) private val disconnectOnFinish by setting("Disconnect On Finish", false, "Disconnects the mover bot when it's finished") { role == Role.MoverBot }.group(Group.General) private val disconnectOnFail by setting("Disconnect On Fail", false, "Disconnects the mover bot if it fails") { role == Role.MoverBot }.group(Group.General) private val startStop by setting("Start/Stop", Bind.EMPTY, "Starts and stops the selected role").group(Group.General) @@ -170,10 +164,10 @@ object StashMover : Module( private var sel2: BlockPos? = null private val pullContainers = hashSetOf() - private val pullEnderChests = hashSetOf() + private val putEnderChests = hashSetOf() private val pulledContainers = hashSetOf() private val putContainers = hashSetOf() - private val putEnderChests = hashSetOf() + private val pullEnderChests = hashSetOf() private val filledContainers = hashSetOf() private var pearlDispensePos: BlockPos? = null @@ -188,8 +182,15 @@ object StashMover : Module( setModulePriority(100) setDefaultAutomationConfig { applyEdits { - buildConfig::checkSideVisibility.edit { defaultValue(true) } + buildConfig.apply { + editTyped(::pathing, ::stayInRange, ::checkSideVisibility) { defaultValue(true) } + hide(::pathing, ::startStop, ::collectDrops, ::spleefEntities, ::entityReach) + hideGroup(eatConfig) + } interactConfig::airPlace.edit { defaultValue(InteractConfig.AirPlaceMode.None) } + breakConfig.apply { + editTyped(::suitableToolsOnly, ::efficientOnly) { defaultValue(false) } + } } } @@ -207,23 +208,10 @@ object StashMover : Module( } } - onDisable { - task?.cancel() - task = null - sel1 = null - sel2 = null - pullContainers.clear() - pulledContainers.clear() - putContainers.clear() - filledContainers.clear() - pearlDispensePos = null - pearlThrowPos = null - pearlRotation = null - pearlBotButton = null - } - - immediateRenderer("StashMover Immediate Renderer") { - if (!chestPullSelMode && !chestPutSelMode) return@immediateRenderer + onDisable { clearModule() } + + tickedRenderer("StashMover Immediate Renderer") { + if (!chestPullSelMode && !chestPutSelMode) return@tickedRenderer sel1?.let { sel1 -> box(Box(sel1)) { colors(Color.PINK.setAlpha(0.1), Color.PINK) @@ -246,6 +234,23 @@ object StashMover : Module( } } + private fun clearModule() { + task?.cancel() + task = null + sel1 = null + sel2 = null + pullContainers.clear() + putEnderChests.clear() + pulledContainers.clear() + putContainers.clear() + pullEnderChests.clear() + filledContainers.clear() + pearlDispensePos = null + pearlThrowPos = null + pearlRotation = null + pearlBotButton = null + } + context(safeContext: SafeContext) fun indexSelectedContainers() { var addCount = 0 @@ -255,11 +260,11 @@ object StashMover : Module( if (useEnderChest && safeContext.blockState(pos).block === Blocks.ENDER_CHEST) { addCount++ if (chestPullSelMode) { - putEnderChests.remove(pos) - pullEnderChests.add(pos) - } else if (chestPutSelMode) { pullEnderChests.remove(pos) putEnderChests.add(pos) + } else if (chestPutSelMode) { + putEnderChests.remove(pos) + pullEnderChests.add(pos) } return@consumeSelection } @@ -338,6 +343,7 @@ object StashMover : Module( } } ) + task = null null } .finally { message -> @@ -350,6 +356,7 @@ object StashMover : Module( } } ) + task = null } .run() } @@ -414,19 +421,22 @@ object StashMover : Module( { finished = true finishedMessage = "Pull containers exhausted!" - moverState = MoverState.MessagingForPearl + moverState = + if (breakEmptyPullContainers) MoverState.BreakingEmptyPullContainers + else MoverState.MessagingForPearl } ) { pos -> pullContainer = pos moverState = MoverState.TakingItems } MoverState.TakingItems -> handleTakingItems(screenHandler) - MoverState.OpeningPullEnderChest -> + MoverState.OpeningPutEnderChest -> openClosestContainer( - pullEnderChests, + putEnderChests, { failWithLog("No pull ender chests indexed!") } ) { moverState = MoverState.PuttingInEnderChest } MoverState.PuttingInEnderChest -> handlePuttingInEnderChest(screenHandler) + MoverState.BreakingEmptyPullContainers -> handleBreakingEmptyPullContainers() MoverState.MessagingForPearl -> handleMessagingForPearl() MoverState.AwaitingTeleport -> checkTimerProgress( @@ -442,9 +452,9 @@ object StashMover : Module( moverState = MoverState.PuttingItems } MoverState.PuttingItems -> handlePuttingItems(screenHandler) - MoverState.OpeningPutEnderChest -> + MoverState.OpeningPullEnderChest -> openClosestContainer( - putEnderChests, + pullEnderChests, { failWithLog("No put ender chests indexed!") } ) { moverState = MoverState.PullingFromEnderChest } MoverState.PullingFromEnderChest -> handlePullingFromEnderChest(screenHandler) @@ -512,7 +522,11 @@ object StashMover : Module( } } if (useEnderChest && EnderChestContainer.stacks.any { it.isEmpty }) { - moverState = MoverState.OpeningPullEnderChest + moverState = MoverState.OpeningPutEnderChest + return + } + if (breakEmptyPullContainers) { + moverState = MoverState.BreakingEmptyPullContainers return } moverState = MoverState.MessagingForPearl @@ -520,7 +534,7 @@ object StashMover : Module( private fun SafeContext.handlePuttingInEnderChest(screenHandler: ScreenHandler) { if (screenHandler === player.playerScreenHandler) { - moverState = MoverState.OpeningPullEnderChest + moverState = MoverState.OpeningPutEnderChest return } if (moveFromContainerToContainer(player.hotbarAndInventorySlots, screenHandler.containerStacks)) { @@ -528,6 +542,21 @@ object StashMover : Module( } } + private fun handleBreakingEmptyPullContainers() { + pulledContainers + .associateWith { TargetState.Empty } + .build() + .onFailOrNull { + failWithLog("Failed to break empty pull containers!") + null + } + .finally { + pulledContainers.clear() + moverState = MoverState.MessagingForPearl + } + .execute(this) + } + private fun SafeContext.handleMessagingForPearl() { tickTimer.reset() connection.sendChatCommand("msg $pearlBotName ${Math.random() * Double.MAX_VALUE}") @@ -540,7 +569,6 @@ object StashMover : Module( return } if (!moveFromContainerToContainer(player.hotbarAndInventorySlots, screenHandler.containerStacks)) return - player.closeHandledScreen() putContainer?.let { container -> if (screenHandler.containerStacks.all { !it.isEmpty }) { putContainers.remove(container) @@ -552,7 +580,7 @@ object StashMover : Module( } } if (useEnderChest && EnderChestContainer.stacks.any { !it.isEmpty }) { - moverState = MoverState.OpeningPutEnderChest + moverState = MoverState.OpeningPullEnderChest return } if (finished) { @@ -564,7 +592,7 @@ object StashMover : Module( private fun SafeContext.handlePullingFromEnderChest(screenHandler: ScreenHandler) { if (screenHandler === player.playerScreenHandler) { - moverState = MoverState.OpeningPutEnderChest + moverState = MoverState.OpeningPullEnderChest return } if (moveFromContainerToContainer(screenHandler.containerSlots, player.hotbarAndInventoryStacks)) { @@ -660,7 +688,10 @@ object StashMover : Module( ): Boolean { val filteredFrom = from.filter { !it.stack.isEmpty } val filteredTo = to.filter { it.isEmpty } - if (filteredTo.isEmpty() || filteredFrom.isEmpty()) return true + if (filteredTo.isEmpty() || filteredFrom.isEmpty()) { + player.closeHandledScreen() + return true + } val moveSlots = filteredFrom.subList(0, min(filteredTo.size, filteredFrom.size)) if (moveSlots.isNotEmpty()) { val request = inventoryRequest(settleForLess = true) { @@ -687,13 +718,14 @@ object StashMover : Module( private enum class MoverState { OpeningPullContainer, TakingItems, - OpeningPullEnderChest, + OpeningPutEnderChest, PuttingInEnderChest, + BreakingEmptyPullContainers, MessagingForPearl, AwaitingTeleport, OpeningPutContainer, PuttingItems, - OpeningPutEnderChest, + OpeningPullEnderChest, PullingFromEnderChest, DispensingPearl, AwaitingPearl, diff --git a/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt b/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt index 8d5fbf5a8..9a1e9aaec 100644 --- a/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt @@ -104,6 +104,7 @@ class OpenContainerTask @Ta5kBuilder constructor( if (interactConfig.rotate && !rotationRequest { rotation(checkedHit.rotation) }.submit().done) return@listen interaction.interactBlock(player, Hand.MAIN_HAND, checkedHit.hit.blockResult ?: return@listen) + player.swingHand(Hand.MAIN_HAND) containerState = State.Opening } From c483a456004d2aa4843a1a155d5561f02f309001 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Sun, 12 Apr 2026 17:38:14 +0100 Subject: [PATCH 11/19] pause/unpause bind/command --- .../lambda/command/commands/StashMoverCommand.kt | 4 ++++ .../com/lambda/module/modules/world/StashMover.kt | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/lambda/command/commands/StashMoverCommand.kt b/src/main/kotlin/com/lambda/command/commands/StashMoverCommand.kt index 558a56e93..acdb95d97 100644 --- a/src/main/kotlin/com/lambda/command/commands/StashMoverCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/StashMoverCommand.kt @@ -27,6 +27,7 @@ import com.lambda.util.extension.CommandBuilder object StashMoverCommand : LambdaCommand( name = "stashmover", + usage = "stashmover ", description = "Set configurations for the StashMover module" ) { override fun CommandBuilder.create() { @@ -48,5 +49,8 @@ object StashMoverCommand : LambdaCommand( required(literal("start-stop")) { execute { runSafe { StashMover.startStop() } } } + required(literal("pause-unpause")) { + execute { runSafe { StashMover.pauseUnpause() } } + } } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt index 72a77aedf..50a6c12b3 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -50,7 +50,6 @@ import com.lambda.task.tasks.OpenContainerTask import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockEntity import com.lambda.util.BlockUtils.blockState -import com.lambda.util.BlockUtils.emptyState import com.lambda.util.Communication.info import com.lambda.util.Communication.logError import com.lambda.util.Communication.warn @@ -96,7 +95,7 @@ object StashMover : Module( description = "Moves items from one stash location to another", tag = ModuleTag.WORLD ) { - private enum class Role(val createTask: () -> Task<*>) { + enum class Role(val createTask: () -> Task<*>) { MoverBot({ MoverBot() }), PearlBot({ PearlBot() }) } @@ -106,7 +105,7 @@ object StashMover : Module( CommandBinds("Command Binds") } - private val role: Role by setting("Role", Role.MoverBot).group(Group.General) + val role: Role by setting("Role", Role.MoverBot).group(Group.General) .onValueChange { _, to -> if (to == Role.PearlBot) clearModule() } private val pearlBotName by setting("PearlBot Name", "Steve") { role == Role.MoverBot }.group(Group.General) private val moverBotName by setting("MoverBot Name", "Steve") { role == Role.PearlBot }.group(Group.General) @@ -133,6 +132,11 @@ object StashMover : Module( event.cancel() startStop() } + private val pauseUnpause by setting("Pause/Unpause", Bind.EMPTY, "Pauses and unpauses the selected role").group(Group.General) + .onPress { event -> + event.cancel() + pauseUnpause() + } private val indexSelectedContainers by setting("Index Selected Containers", Bind.EMPTY, "Indexes the selected containers to pull/push items from/to") { role == Role.MoverBot }.group(Group.CommandBinds) .onPress { event -> @@ -361,6 +365,11 @@ object StashMover : Module( .run() } + fun pauseUnpause() { + if (task?.isMuted == true) task?.activate() + else task?.pause() + } + private fun consumeSelection(callback: (pos: BlockPos) -> Unit) { sel1?.let { sel1 -> sel2?.let { sel2 -> From ddf639862aeaa73ac0da9de8e5ad0a01506f8dde Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Sun, 12 Apr 2026 20:48:04 +0100 Subject: [PATCH 12/19] different modes for dropping off items. Chests, Drop, and Death, but Death isnt implemented yet --- .../command/commands/StashMoverCommand.kt | 3 + .../lambda/module/modules/world/StashMover.kt | 106 ++++++++++++++---- 2 files changed, 86 insertions(+), 23 deletions(-) diff --git a/src/main/kotlin/com/lambda/command/commands/StashMoverCommand.kt b/src/main/kotlin/com/lambda/command/commands/StashMoverCommand.kt index acdb95d97..de7ae5cd4 100644 --- a/src/main/kotlin/com/lambda/command/commands/StashMoverCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/StashMoverCommand.kt @@ -37,6 +37,9 @@ object StashMoverCommand : LambdaCommand( required(literal("remove_selected_containers")) { execute { runSafe { StashMover.removeSelectedContainers() } } } + required(literal("set_item_throw")) { + execute { runSafe { StashMover.setItemThrow() } } + } required(literal("set_pearl_button_pos")) { execute { runSafe { StashMover.setPearlButtonPos() } } } diff --git a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt index 50a6c12b3..dde9cbdeb 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -39,6 +39,7 @@ import com.lambda.interaction.managers.interacting.InteractConfig import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.managers.rotating.Rotation.Companion.dist import com.lambda.interaction.managers.rotating.RotationManager import com.lambda.interaction.material.container.containers.EnderChestContainer import com.lambda.module.Module @@ -95,20 +96,27 @@ object StashMover : Module( description = "Moves items from one stash location to another", tag = ModuleTag.WORLD ) { + private enum class Group(override val displayName: String) : NamedEnum { + General("General"), + CommandBinds("Command Binds") + } + enum class Role(val createTask: () -> Task<*>) { MoverBot({ MoverBot() }), PearlBot({ PearlBot() }) } - private enum class Group(override val displayName: String) : NamedEnum { - General("General"), - CommandBinds("Command Binds") + private enum class DropOffMode(override val displayName: String) : NamedEnum { + Chests("Chests"), + Drop("Drop"), + Death("Death") } val role: Role by setting("Role", Role.MoverBot).group(Group.General) .onValueChange { _, to -> if (to == Role.PearlBot) clearModule() } private val pearlBotName by setting("PearlBot Name", "Steve") { role == Role.MoverBot }.group(Group.General) private val moverBotName by setting("MoverBot Name", "Steve") { role == Role.PearlBot }.group(Group.General) + private val dropOffMode by setting("Drop-Off Mode", DropOffMode.Chests) { role == Role.MoverBot }.group(Group.General) private var chestPullSelMode: Boolean by setting("Chest Pull Sel Mode", false, "Enables the mode to select the stash containers you want to move items from") { role == Role.MoverBot }.group(Group.General) .onValueChange { _, to -> if (to) { chestPutSelMode = false; StashMover.info("Enabled chest pull selection mode!") } } private var chestPutSelMode: Boolean by setting("Chest Put Sel Mode", false, "Enables the mod to select the stash containers you want to move items into") { role == Role.MoverBot }.group(Group.General) @@ -148,6 +156,11 @@ object StashMover : Module( event.cancel() removeSelectedContainers() } + private val setItemThrowPosAndRotation by setting("Set Item Throw", Bind.EMPTY, "Sets the item throw position and rotation. (This is usually set to throw into hoppers to pickup the items)") { role == Role.MoverBot }.group(Group.CommandBinds) + .onPress { event -> + event.cancel() + setItemThrow() + } private val setPearlButtonPos by setting("Set Pearl Button Pos", Bind.EMPTY, "Sets the button used to dispense a pearl for the player") { role == Role.MoverBot }.group(Group.CommandBinds) .onPress { event -> event.cancel() @@ -174,6 +187,9 @@ object StashMover : Module( private val pullEnderChests = hashSetOf() private val filledContainers = hashSetOf() + private var itemThrowPos: BlockPos? = null + private var itemThrowRotation: Rotation? = null + private var pearlDispensePos: BlockPos? = null private var pearlThrowPos: BlockPos? = null private var pearlRotation: Rotation? = null @@ -298,6 +314,12 @@ object StashMover : Module( StashMover.info("Removed $removeCount containers!") } + context(safeContext: SafeContext) + fun setItemThrow() { + itemThrowPos = safeContext.player.blockPos + itemThrowRotation = safeContext.player.rotation + } + context(safeContext: SafeContext) fun setPearlButtonPos() { val pos = mc.crosshairTarget?.blockResult?.blockPos ?: return @@ -430,9 +452,7 @@ object StashMover : Module( { finished = true finishedMessage = "Pull containers exhausted!" - moverState = - if (breakEmptyPullContainers) MoverState.BreakingEmptyPullContainers - else MoverState.MessagingForPearl + breakPulledOrPearl() } ) { pos -> pullContainer = pos @@ -452,6 +472,7 @@ object StashMover : Module( MoverState.MessagingForPearl, pearlMsgTimeout ) + MoverState.DroppingItems -> handleDroppingItems() MoverState.OpeningPutContainer -> openClosestContainer( putContainers, @@ -504,7 +525,7 @@ object StashMover : Module( success(finishedMessage) return@listen } - moverState = MoverState.OpeningPutContainer + putOrThrowItems() } listen { event -> @@ -530,15 +551,11 @@ object StashMover : Module( } } } - if (useEnderChest && EnderChestContainer.stacks.any { it.isEmpty }) { + if (useEnderChest && (EnderChestContainer.stacks.isEmpty() || EnderChestContainer.stacks.any { it.isEmpty })) { moverState = MoverState.OpeningPutEnderChest return } - if (breakEmptyPullContainers) { - moverState = MoverState.BreakingEmptyPullContainers - return - } - moverState = MoverState.MessagingForPearl + breakPulledOrPearl() } private fun SafeContext.handlePuttingInEnderChest(screenHandler: ScreenHandler) { @@ -572,6 +589,30 @@ object StashMover : Module( moverState = MoverState.AwaitingTeleport } + private fun SafeContext.handleDroppingItems() { + val throwPos = itemThrowPos ?: run { failWithLog("No item throw pos set!"); return } + if (player.blockPos != throwPos) { + BaritoneManager.setGoalAndPath(GoalBlock(throwPos)) + return + } + if (BaritoneManager.isActive) return + val rotation = itemThrowRotation ?: run { failWithLog("No item throw rotation set!"); return } + val rotationRequest = rotationRequest { + rotation(rotation) + }.submit() + if (!rotationRequest.done || rotation dist RotationManager.serverRotation > 0.001) return + val throwSlots = player.hotbarAndInventorySlots.filter { !it.stack.isEmpty } + if (throwSlots.isNotEmpty()) { + val inventoryRequest = inventoryRequest(settleForLess = true) { + throwSlots.forEach { slot -> + throwStack(slot.id) + } + }.submit() + if (!inventoryRequest.done) return + } + pullFromEnderChestOrContinue() + } + private fun SafeContext.handlePuttingItems(screenHandler: ScreenHandler) { if (screenHandler === player.playerScreenHandler) { moverState = MoverState.OpeningPutContainer @@ -588,15 +629,7 @@ object StashMover : Module( } } } - if (useEnderChest && EnderChestContainer.stacks.any { !it.isEmpty }) { - moverState = MoverState.OpeningPullEnderChest - return - } - if (finished) { - success(finishedMessage) - return - } - moverState = MoverState.DispensingPearl + pullFromEnderChestOrContinue() } private fun SafeContext.handlePullingFromEnderChest(screenHandler: ScreenHandler) { @@ -605,7 +638,7 @@ object StashMover : Module( return } if (moveFromContainerToContainer(screenHandler.containerSlots, player.hotbarAndInventoryStacks)) { - moverState = MoverState.OpeningPutContainer + putOrThrowItems() } } @@ -672,6 +705,32 @@ object StashMover : Module( moverState = MoverState.AwaitingRespawn } + private fun breakPulledOrPearl() { + moverState = + if (breakEmptyPullContainers && pulledContainers.isNotEmpty()) MoverState.BreakingEmptyPullContainers + else MoverState.MessagingForPearl + } + + private fun pullFromEnderChestOrContinue() { + if (useEnderChest && (EnderChestContainer.stacks.isEmpty() || EnderChestContainer.stacks.any { !it.isEmpty })) { + moverState = MoverState.OpeningPullEnderChest + return + } + if (finished) { + success(finishedMessage) + return + } + moverState = MoverState.DispensingPearl + } + + private fun putOrThrowItems() { + moverState = + when (dropOffMode) { + DropOffMode.Chests -> MoverState.OpeningPutContainer + else -> MoverState.DroppingItems + } + } + private fun SafeContext.openClosestContainer( positions: Collection, onNoneAvailable: () -> Unit, @@ -732,6 +791,7 @@ object StashMover : Module( BreakingEmptyPullContainers, MessagingForPearl, AwaitingTeleport, + DroppingItems, OpeningPutContainer, PuttingItems, OpeningPullEnderChest, From 4bcd704de3eca7ca2f10dc4e653203c042f9063e Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Sun, 12 Apr 2026 22:26:04 +0100 Subject: [PATCH 13/19] move pearling to after being pearled to the new stash, before dropping off the items and removed death mode --- .../lambda/module/modules/world/StashMover.kt | 76 +++++++++++++------ 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt index dde9cbdeb..e94b8be0b 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -65,6 +65,9 @@ import com.lambda.util.player.SlotUtils.allSlots import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks import com.lambda.util.player.SlotUtils.hotbarSlots +import com.lambda.util.player.SlotUtils.hotbarStacks +import com.lambda.util.player.SlotUtils.inventoryStacks +import com.lambda.util.player.SlotUtils.offHandSlots import com.lambda.util.text.bold import com.lambda.util.text.buildText import com.lambda.util.text.color @@ -108,8 +111,7 @@ object StashMover : Module( private enum class DropOffMode(override val displayName: String) : NamedEnum { Chests("Chests"), - Drop("Drop"), - Death("Death") + Drop("Drop") } val role: Role by setting("Role", Role.MoverBot).group(Group.General) @@ -427,6 +429,8 @@ object StashMover : Module( private var actionDelayTimer = TickTimer() private var delayingNextAction = false + private var pearlThrown = false + private var pullContainer: BlockPos? = null private var tickTimer = TickTimer() @@ -472,6 +476,16 @@ object StashMover : Module( MoverState.MessagingForPearl, pearlMsgTimeout ) + MoverState.DispensingPearl -> handleDispensingPearl() + MoverState.AwaitingPearl -> + checkTimerProgress(MoverState.DispensingPearl, pearlButtonTimeout) { + if (player.hotbarAndInventoryStacks.any { it.item === Items.ENDER_PEARL }) { + pearlThrown = false + moverState = MoverState.ThrowingPearl + true + } else false + } + MoverState.ThrowingPearl -> handleThrowingPearl() MoverState.DroppingItems -> handleDroppingItems() MoverState.OpeningPutContainer -> openClosestContainer( @@ -488,15 +502,6 @@ object StashMover : Module( { failWithLog("No put ender chests indexed!") } ) { moverState = MoverState.PullingFromEnderChest } MoverState.PullingFromEnderChest -> handlePullingFromEnderChest(screenHandler) - MoverState.DispensingPearl -> handleDispensingPearl() - MoverState.AwaitingPearl -> - checkTimerProgress(MoverState.DispensingPearl, pearlButtonTimeout) { - if (player.hotbarAndInventoryStacks.any { it.item === Items.ENDER_PEARL }) { - moverState = MoverState.ThrowingPearl - true - } else false - } - MoverState.ThrowingPearl -> handleThrowingPearl() MoverState.Killing -> handleKilling() else -> {} } @@ -525,7 +530,7 @@ object StashMover : Module( success(finishedMessage) return@listen } - putOrThrowItems() + moverState = MoverState.DispensingPearl } listen { event -> @@ -649,6 +654,18 @@ object StashMover : Module( return } if (BaritoneManager.isActive) return + if (player.hotbarStacks.none { it.isEmpty }) { + val firstSlot = player.hotbarSlots.getOrNull(0) ?: run { failWithLog("No first slot? This shouldn't occur."); return } + if (player.inventoryStacks.any { it.isEmpty }) { + inventoryRequest { quickMove(firstSlot.id) }.submit() + return + } else if (player.offHandStack.isEmpty) { + inventoryRequest { swap(firstSlot.id, 40) }.submit() + return + } + failWithLog("No free slots for an ender pearl!") + return + } getButtonPressTask(dispensePos) ?.finally { tickTimer.reset() @@ -664,33 +681,44 @@ object StashMover : Module( return } if (BaritoneManager.isActive) return + + if (pearlThrown) { + if (!player.offHandStack.isEmpty) { + if (player.hotbarAndInventoryStacks.none { it.isEmpty }) { + failWithLog("No free slots to return the offhand stack to!") + return + } + val offhandSlot = player.offHandSlots.firstOrNull() ?: run { failWithLog("No offhand slot? This shouldn't occur."); return } + inventoryRequest { quickMove(offhandSlot.id) }.submit() + } + putOrThrowItems() + return + } + val rotation = pearlRotation ?: run { failWithLog("No pearl rotation set!"); return } val rotationRequest = rotationRequest { rotation(rotation) }.submit() if (!rotationRequest.done) return - while (player.mainHandStack.item != Items.ENDER_PEARL) { + if (player.mainHandStack.item != Items.ENDER_PEARL) { val hotbarSlot = player.hotbarSlots.firstOrNull { it.stack.item === Items.ENDER_PEARL } if (hotbarSlot != null) { val hotbarRequest = HotbarRequest(hotbarSlot.index, StashMover, nowOrNothing = false).submit() if (!hotbarRequest.done) return - break } else { val inventorySlot = player.allSlots.firstOrNull { it.stack.item === Items.ENDER_PEARL } if (inventorySlot == null) { failWithLog("No pearl in inventory!") return } - val inventoryRequest = inventoryRequest { - swap(inventorySlot.id, 0) - }.submit() - if (!inventoryRequest.done) return + inventoryRequest { swap(inventorySlot.id, 0) }.submit() + return } } RotationManager.withoutVanillaOverrides { interaction.interactItem(player, Hand.MAIN_HAND) + pearlThrown = true } - moverState = MoverState.Killing } private fun SafeContext.handleKilling() { @@ -720,14 +748,14 @@ object StashMover : Module( success(finishedMessage) return } - moverState = MoverState.DispensingPearl + moverState = MoverState.Killing } private fun putOrThrowItems() { moverState = when (dropOffMode) { DropOffMode.Chests -> MoverState.OpeningPutContainer - else -> MoverState.DroppingItems + DropOffMode.Drop -> MoverState.DroppingItems } } @@ -791,14 +819,14 @@ object StashMover : Module( BreakingEmptyPullContainers, MessagingForPearl, AwaitingTeleport, + DispensingPearl, + AwaitingPearl, + ThrowingPearl, DroppingItems, OpeningPutContainer, PuttingItems, OpeningPullEnderChest, PullingFromEnderChest, - DispensingPearl, - AwaitingPearl, - ThrowingPearl, Killing, AwaitingDeath, Respawning, From b704eea67aab016b4367d2b59c0d0f949484a098 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:08:35 +0100 Subject: [PATCH 14/19] y velocity check before throwing pearl --- src/main/kotlin/com/lambda/module/modules/world/StashMover.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt index e94b8be0b..4e6092a41 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -94,6 +94,7 @@ import kotlin.math.min import kotlin.run import kotlin.to +@Suppress("unused") object StashMover : Module( name = "StashMover", description = "Moves items from one stash location to another", @@ -206,7 +207,7 @@ object StashMover : Module( applyEdits { buildConfig.apply { editTyped(::pathing, ::stayInRange, ::checkSideVisibility) { defaultValue(true) } - hide(::pathing, ::startStop, ::collectDrops, ::spleefEntities, ::entityReach) + hide(::pathing, ::stayInRange, ::collectDrops, ::spleefEntities, ::entityReach) hideGroup(eatConfig) } interactConfig::airPlace.edit { defaultValue(InteractConfig.AirPlaceMode.None) } @@ -681,6 +682,7 @@ object StashMover : Module( return } if (BaritoneManager.isActive) return + if (player.velocity.y < -0.08) return if (pearlThrown) { if (!player.offHandStack.isEmpty) { From 0f64d608e1d5b746ac6a5bf81259ca38aeb7080d Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Sat, 18 Apr 2026 18:42:26 +0100 Subject: [PATCH 15/19] inline the inventory packet limit with the rest --- .../com/lambda/config/groups/BuildConfig.kt | 1 + .../com/lambda/config/groups/BuildSettings.kt | 3 +- .../managers/PacketLimitHandler.kt | 3 +- .../managers/inventory/InventoryManager.kt | 31 ++++++++----------- .../lambda/module/modules/world/Scaffold.kt | 2 +- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt b/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt index 93896f003..8752d08c9 100644 --- a/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt +++ b/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt @@ -38,6 +38,7 @@ interface BuildConfig : ISettingGroup { val limitTimeframe: Int val actionLimit: Int val interactionLimit: Int + val inventoryLimit: Int val blockReach: Double val entityReach: Double diff --git a/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt b/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt index 48593da33..3f3262f45 100644 --- a/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt @@ -50,7 +50,8 @@ class BuildSettings( override val limitTimeframe by c.setting("${prefix}Limit Timeframe", 310, 50..1500, 1, "The timeframe in which the limit is bound to", "ms", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() override val actionLimit by c.setting("${prefix}Action Limit", 59, 1..100, 1, "The maximum allowed action packets to be sent to the server per given timeframe", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() - override val interactionLimit by c.setting("Interaction Limit", 9, 1..20, 1, "The maximum allowed interaction packets to be sent to the server per given timeframe", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() + override val interactionLimit by c.setting("${prefix}Interaction Limit", 9, 1..20, 1, "The maximum allowed interaction packets to be sent to the server per given timeframe", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() + override val inventoryLimit by c.setting("${prefix}Inventory Limit", 5, 1..100, 1, "The maximum allowed inventory packets to be sent to the server per given timeframe", visibility = visibility).group(*baseGroup, Group.PacketLimits).index() override var blockReach by c.setting("${prefix}Interact Reach", 4.5, 1.0..7.0, 0.01, "Maximum block interaction distance", visibility = visibility).group(*baseGroup, Group.Reach).index() override var entityReach by c.setting("${prefix}Attack Reach", 3.0, 1.0..7.0, 0.01, "Maximum entity interaction distance", visibility = visibility).group(*baseGroup, Group.Reach).index() diff --git a/src/main/kotlin/com/lambda/interaction/managers/PacketLimitHandler.kt b/src/main/kotlin/com/lambda/interaction/managers/PacketLimitHandler.kt index 4d32cd3a7..bd0407a22 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/PacketLimitHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/PacketLimitHandler.kt @@ -70,5 +70,6 @@ object PacketLimitHandler { enum class PacketType(val maxPacketsPerTimeframe: BuildConfig.() -> Int) { PlayerAction({ actionLimit }), - Interaction({ interactionLimit }) + Interaction({ interactionLimit }), + Inventory({ inventoryLimit }) } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt index e9ee30315..1d485a1c0 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt @@ -23,7 +23,8 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.managers.Manager -import com.lambda.interaction.managers.PacketLimitHandler +import com.lambda.interaction.managers.PacketLimitHandler.canSendPackets +import com.lambda.interaction.managers.PacketLimitHandler.sentPackets import com.lambda.interaction.managers.PacketType import com.lambda.interaction.managers.inventory.InventoryManager.actions import com.lambda.interaction.managers.inventory.InventoryManager.activeRequest @@ -31,6 +32,7 @@ import com.lambda.interaction.managers.inventory.InventoryManager.alteredSlots import com.lambda.interaction.managers.inventory.InventoryManager.processActiveRequest import com.lambda.module.modules.client.Client import com.lambda.threading.runSafe +import com.lambda.threading.runSafeAutomated import com.lambda.util.collections.LimitedDecayQueue import com.lambda.util.item.ItemStackUtils.equal import com.llamalad7.mixinextras.injector.wrapoperation.Operation @@ -67,9 +69,6 @@ object InventoryManager : Manager( field = value } - private var maxActionsThisSecond = 0 - private var actionsThisSecond = 0 - private var secondCounter = 0 private var actionsThisTick = 0 override fun load(): String { @@ -77,10 +76,6 @@ object InventoryManager : Manager( listen({ Int.MIN_VALUE }) { if (Client.avoidInventoryDesync) indexInventoryChanges() - if (++secondCounter >= 20) { - secondCounter = 0 - actionsThisSecond = 0 - } actionsThisTick = 0 activeRequest = null actions = mutableListOf() @@ -105,11 +100,11 @@ object InventoryManager : Manager( override fun AutomatedSafeContext.handleRequest(request: InventoryRequest) { if (activeRequest != null) return - val inventoryActionCount = request.actions.count { it is InventoryAction.Inventory } val playerActionCount = request.actions.count { it is InventoryAction.Player } - val canPerformAllPlayerActions = PacketLimitHandler.canSendPackets(playerActionCount, PacketType.PlayerAction) - val canPerformAllInventoryActions = inventoryActionCount <= request.inventoryConfig.actionsPerSecond - actionsThisSecond - if ((!canPerformAllInventoryActions || !canPerformAllPlayerActions) && + val inventoryActionCount = request.actions.count { it is InventoryAction.Inventory } + val canPerformAllPlayerActions = canSendPackets(playerActionCount, PacketType.PlayerAction) + val canPerformAllInventoryActions = canSendPackets(inventoryActionCount, PacketType.Inventory) + if ((!canPerformAllPlayerActions || !canPerformAllInventoryActions) && !request.settleForLess && !request.mustPerform) return @@ -125,7 +120,6 @@ object InventoryManager : Manager( private fun populateFrom(request: InventoryRequest) { activeRequest = request actions = request.actions.toMutableList() - maxActionsThisSecond = request.inventoryConfig.actionsPerSecond alteredSlots.setDecayTime(Client.desyncTimeout * 50L) alteredPlayerSlots.setDecayTime(Client.desyncTimeout * 50L) } @@ -136,18 +130,19 @@ object InventoryManager : Manager( * The [activeRequest] is then set to null. */ private fun SafeContext.processActiveRequest() { - activeRequest?.let { active -> + val active = activeRequest ?: return + active.runSafeAutomated { if (tickStage !in active.inventoryConfig.tickStageMask && active.nowOrNothing) return val iterator = actions.iterator() while (iterator.hasNext()) { val action = iterator.next() - if (action is InventoryAction.Inventory && actionsThisSecond + 1 > maxActionsThisSecond && !active.mustPerform) - break + if (action is InventoryAction.Player && !canSendPackets(1, PacketType.PlayerAction)) break + else if (action is InventoryAction.Inventory && !canSendPackets(1, PacketType.Inventory)) break action.action(this) - if (action is InventoryAction.Player) PacketLimitHandler.sentPackets(1, PacketType.PlayerAction) + if (action is InventoryAction.Player) sentPackets(1, PacketType.PlayerAction) + else if (action is InventoryAction.Inventory) sentPackets(1, PacketType.Inventory) if (Client.avoidInventoryDesync) indexInventoryChanges() actionsThisTick++ - if (action is InventoryAction.Inventory) actionsThisSecond++ iterator.remove() } diff --git a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt index 7a5fe1c96..d1135dfaa 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt @@ -62,6 +62,7 @@ object Scaffold : Module( defaultValue(false) hide() } + hide(::inventoryLimit, ::breakBlocks) } rotationConfig.apply { ::instant.edit { defaultValue(false) } @@ -70,7 +71,6 @@ object Scaffold : Module( } inventoryConfig.apply { hide( - ::actionsPerSecond, ::tickStageMask, ::swapWithDisposables, ::providerPriority, From 8375f69fb67233bc951263bb57e55c5623d87db6 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Sat, 18 Apr 2026 19:11:07 +0100 Subject: [PATCH 16/19] more velo checks --- src/main/kotlin/com/lambda/module/modules/world/StashMover.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt index 4e6092a41..e2add27cb 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -682,7 +682,7 @@ object StashMover : Module( return } if (BaritoneManager.isActive) return - if (player.velocity.y < -0.08) return + if (player.velocity.y < -0.08 || player.velocity.x !in -0.001..0.001 || player.velocity.z !in -0.001..0.001) return if (pearlThrown) { if (!player.offHandStack.isEmpty) { From 586e7f4dada1e5577684204ec1651a11bf458a14 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Sat, 18 Apr 2026 19:47:42 +0100 Subject: [PATCH 17/19] enabled checks for commands --- .../kotlin/com/lambda/module/modules/world/StashMover.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt index e2add27cb..833f6b541 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/StashMover.kt @@ -116,7 +116,7 @@ object StashMover : Module( } val role: Role by setting("Role", Role.MoverBot).group(Group.General) - .onValueChange { _, to -> if (to == Role.PearlBot) clearModule() } + .onValueChange { _, _ -> clearModule() } private val pearlBotName by setting("PearlBot Name", "Steve") { role == Role.MoverBot }.group(Group.General) private val moverBotName by setting("MoverBot Name", "Steve") { role == Role.PearlBot }.group(Group.General) private val dropOffMode by setting("Drop-Off Mode", DropOffMode.Chests) { role == Role.MoverBot }.group(Group.General) @@ -272,6 +272,8 @@ object StashMover : Module( pearlThrowPos = null pearlRotation = null pearlBotButton = null + itemThrowPos = null + itemThrowRotation = null } context(safeContext: SafeContext) @@ -353,6 +355,7 @@ object StashMover : Module( } fun startStop() { + if (isDisabled) return chestPullSelMode = false chestPutSelMode = false task?.let { runningTask -> @@ -391,6 +394,7 @@ object StashMover : Module( } fun pauseUnpause() { + if (isDisabled) return if (task?.isMuted == true) task?.activate() else task?.pause() } From f646880f915218bb1440298722457f95b9d15a39 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Sat, 18 Apr 2026 19:53:33 +0100 Subject: [PATCH 18/19] inventory config is technically relied on --- src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt index 26b3b099f..2e8ffcbfa 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt @@ -64,7 +64,7 @@ object Scaffold : Module( hide() } ::checkSideVisibility.edit { defaultValue(true) } - hide(::inventoryLimit, ::breakBlocks) + hide(::breakBlocks) } interactConfig::airPlace.edit { defaultValue(InteractConfig.AirPlaceMode.None) } rotationConfig.apply { From c9cca3cda5d70f76fd065cfef42cd21fd9824192 Mon Sep 17 00:00:00 2001 From: beanbag44 <107891830+beanbag44@users.noreply.github.com> Date: Sat, 18 Apr 2026 19:56:58 +0100 Subject: [PATCH 19/19] remove actionsPerSecond setting --- .../com/lambda/config/groups/InventorySettings.kt | 1 - .../managers/inventory/InventoryConfig.kt | 1 - .../com/lambda/module/modules/world/Scaffold.kt | 15 +-------------- 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt b/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt index c71898ba2..6b4fac6c9 100644 --- a/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt @@ -36,7 +36,6 @@ class InventorySettings( Access("Access") } - override val actionsPerSecond by c.setting("${prefix}Actions Per Second", 18, 0..100, 1, "How many inventory actions can be performed per tick", visibility = visibility).group(*baseGroup, Group.General).index() override val tickStageMask by c.setting("${prefix}Inventory Stage Mask", ALL_STAGES.toSet(), description = "The sub-tick timing at which inventory actions are performed", displayClassName = true, visibility = visibility).group(*baseGroup, Group.General).index() override val disposables by c.setting("${prefix}Disposables", ItemUtils.defaultDisposables, description = "Items that will be ignored when checking for a free slot", visibility = visibility).group(*baseGroup, Group.Container).index() override val swapWithDisposables by c.setting("${prefix}Swap With Disposables", true, "Swap items with disposable ones", visibility = visibility).group(*baseGroup, Group.Container).index() diff --git a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryConfig.kt b/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryConfig.kt index dcc5f4d89..d62b02de1 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryConfig.kt @@ -28,7 +28,6 @@ import com.lambda.util.NamedEnum import net.minecraft.item.Item interface InventoryConfig : ISettingGroup { - val actionsPerSecond: Int val tickStageMask: Collection val disposables: Collection val swapWithDisposables: Boolean diff --git a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt index 2e8ffcbfa..a1e6cea1a 100644 --- a/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/world/Scaffold.kt @@ -72,20 +72,7 @@ object Scaffold : Module( ::mean.edit { defaultValue(120.0) } ::spread.edit { defaultValue(0.0) } } - inventoryConfig.apply { - hide( - ::tickStageMask, - ::swapWithDisposables, - ::providerPriority, - ::storePriority, - ::accessShulkerBoxes, - ::accessEnderChest, - ::accessChests, - ::accessStashes, - ::disposables - ) - } - hideAllGroupsExcept(buildConfig, interactConfig, rotationConfig, hotbarConfig, inventoryConfig) + hideAllGroupsExcept(buildConfig, interactConfig, rotationConfig, hotbarConfig) } }